Improve error management

- Better handle connection errors.
- Better handle login errors.
- Improve exception process.
- Update examples.
	- Make examples compatible with new configuration file.
	- Show exception handling.
- Validate trusted SSL connection.
- Various PEP8 corrections.
This commit is contained in:
Uggla 2016-01-02 11:33:58 +01:00
parent 4ef8d92da7
commit 466534359c
5 changed files with 131 additions and 61 deletions

View File

@ -6,7 +6,7 @@ import os
import sys import sys
import json import json
import redfish import redfish
from time import sleep
# Get $HOME environment. # Get $HOME environment.
HOME = os.getenv('HOME') HOME = os.getenv('HOME')
@ -24,17 +24,26 @@ except IOError as e:
print(e) print(e)
sys.exit(1) sys.exit(1)
URL = config["Nodes"]["default"]["url"] URL = config["Managers"]["default"]["url"]
USER_NAME = config["Nodes"]["default"]["login"] USER_NAME = config["Managers"]["default"]["login"]
PASSWORD = config["Nodes"]["default"]["password"] PASSWORD = config["Managers"]["default"]["password"]
''' remote_mgmt is a redfish.RedfishConnection object ''' ''' remote_mgmt is a redfish.RedfishConnection object '''
remote_mgmt = redfish.connect(URL, USER_NAME, PASSWORD, verify_cert=False) try:
remote_mgmt = redfish.connect(URL,
USER_NAME,
PASSWORD,
simulator=False,
verify_cert=False)
except redfish.exception.RedfishException as e:
sys.stderr.write(str(e.message))
sys.stderr.write(str(e.advices))
sys.exit(1)
print ("Redfish API version : %s \n" % remote_mgmt.get_api_version()) print ("Redfish API version : %s \n" % remote_mgmt.get_api_version())
# Uncomment following line to reset the blade !!! # Uncomment following line to reset the blade !!!
#remote_mgmt.Systems.systems_list[0].reset_system() # remote_mgmt.Systems.systems_list[0].reset_system()
# TODO : create an attribute to link the managed system directly # TODO : create an attribute to link the managed system directly
# and avoid systems_list[0] # and avoid systems_list[0]

View File

@ -23,13 +23,22 @@ except IOError as e:
print(e) print(e)
sys.exit(1) sys.exit(1)
URL = config["Nodes"]["default"]["url"] URL = config["Managers"]["default"]["url"]
USER_NAME = config["Nodes"]["default"]["login"] USER_NAME = config["Managers"]["default"]["login"]
PASSWORD = config["Nodes"]["default"]["password"] PASSWORD = config["Managers"]["default"]["password"]
''' remoteMgmt is a redfish.RedfishConnection object ''' ''' remoteMgmt is a redfish.RedfishConnection object '''
remote_mgmt = redfish.connect(URL, USER_NAME, PASSWORD, try:
simulator=True, enforceSSL=False) remote_mgmt = redfish.connect(URL,
USER_NAME,
PASSWORD,
simulator=True,
enforceSSL=False)
except redfish.exception.RedfishException as e:
sys.stderr.write(e.message)
sys.stderr.write(e.advices)
sys.exit(1)
print("Redfish API version : {} \n".format(remote_mgmt.get_api_version())) print("Redfish API version : {} \n".format(remote_mgmt.get_api_version()))
print("UUID : {} \n".format(remote_mgmt.Root.get_api_UUID())) print("UUID : {} \n".format(remote_mgmt.Root.get_api_UUID()))

View File

@ -1,21 +1,52 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys
import config import config
class RedfishException(Exception): class RedfishException(Exception):
"""Base class for redfish exceptions""" """Base class for redfish exceptions"""
def __init__(self, message=None, **kwargs): def __init__(self, message, **kwargs):
self.kwargs = kwargs self.kwargs = kwargs
self.message = message self.message = message
self.advices = None
config.logger.error(message)
class ConnectionFailureException(RedfishException):
def __init__(self, message, **kwargs):
super(ConnectionFailureException, self).__init__(message, **kwargs)
self.advices = '1- Check if the url is the correct one\n' + \
'2- Check if your device is answering on the network\n'
class InvalidRedfishContentException(RedfishException):
def __init__(self, message, **kwargs):
super(InvalidRedfishContentException, self).__init__(message, **kwargs)
self.advices = \
'1- Check if the url is the correct one\n' + \
' Most of the time you are not pointing to the rest API\n'
class NonTrustedCertificatException(RedfishException):
def __init__(self, message, **kwargs):
super(NonTrustedCertificatException, self).__init__(message, **kwargs)
self.advices = \
'1- Check if the url is the correct one\n' + \
'2- Check if your device has a valid trusted certificat\n' + \
' You can use openssl to validate it using the command :\n' + \
' openssl s_client -showcerts -connect <server>:443\n'
class AuthenticationFailureException(RedfishException): class AuthenticationFailureException(RedfishException):
def __init__(self, message=None, **kwargs): def __init__(self, message, **kwargs):
super(AuthenticationFailureException, self).__init__(message=None, **kwargs) super(AuthenticationFailureException, self).__init__(message, **kwargs)
config.logger.error(message) self.message += str(kwargs['code'])
# TODO self.queryAnswer = kwargs['queryAnswer']
# Give a bit more details about the failure (check login etc...) if kwargs['code'] == 400:
sys.exit(1) self.message += ': ' + self.queryAnswer['Messages'][0]['MessageID']
self.advices = '1- Check your credentials\n'
self.message += '\n'
class LogoutFailureException(RedfishException): class LogoutFailureException(RedfishException):
pass pass

View File

@ -271,7 +271,7 @@ class RedfishConnection(object):
# #
# print self.systemCollection.Name # print self.systemCollection.Name
# #
# ======================================================================== # ========================================================================
def get_api_version(self): def get_api_version(self):
"""Return api version. """Return api version.
@ -286,14 +286,15 @@ class RedfishConnection(object):
url = self.Root.get_link_url( url = self.Root.get_link_url(
mapping.redfish_mapper.map_sessionservice() mapping.redfish_mapper.map_sessionservice()
) )
# Handle login with redfish 1.00, url must be : # Handle login with redfish 1.00, url must be :
# /rest/v1/SessionService/Sessions as specified by the specification # /rest/v1/SessionService/Sessions as specified by the specification
if float(mapping.redfish_version) >= 1.00: if float(mapping.redfish_version) >= 1.00:
url += '/Sessions' url += '/Sessions'
# Craft request body and header # Craft request body and header
requestBody = {"UserName": self.connection_parameters.user_name , "Password": self.connection_parameters.password} requestBody = {"UserName": self.connection_parameters.user_name , "Password": self.connection_parameters.password}
config.logger.debug(requestBody)
header = {'Content-type': 'application/json'} header = {'Content-type': 'application/json'}
# ======================================================================= # =======================================================================
# Tortilla seems not able to provide the header of a post request answer. # Tortilla seems not able to provide the header of a post request answer.
@ -308,13 +309,16 @@ class RedfishConnection(object):
headers=header, headers=header,
verify=self.connection_parameters.verify_cert verify=self.connection_parameters.verify_cert
) )
# ======================================================================= # =======================================================================
# TODO : Manage exception with a class. # TODO : Manage exception with a class.
# ======================================================================= # =======================================================================
if auth.status_code != 201: if auth.status_code != 201:
raise exception.AuthenticationFailureException("Login request return an invalid status code") try:
#sysraise "Error getting token", auth.status_code answer=auth.json()
except ValueError as e:
answer = ""
raise exception.AuthenticationFailureException("Login request return an invalid status code ", code=auth.status_code, queryAnswer=answer)
self.connection_parameters.auth_token = auth.headers.get("x-auth-token") self.connection_parameters.auth_token = auth.headers.get("x-auth-token")
self.connection_parameters.user_uri = auth.headers.get("location") self.connection_parameters.user_uri = auth.headers.get("location")

View File

@ -1,12 +1,14 @@
# coding=utf-8 # coding=utf-8
import pprint import pprint
import re
from urlparse import urljoin from urlparse import urljoin
import requests import requests
import simplejson
import tortilla import tortilla
import config import config
import mapping import mapping
import re import exception
# Global variable # Global variable
@ -28,10 +30,25 @@ class Base(object):
headers={'x-auth-token': connection_parameters.auth_token} headers={'x-auth-token': connection_parameters.auth_token}
) )
except requests.ConnectionError as e: except requests.ConnectionError as e:
print e
# Log and transmit the exception. # Log and transmit the exception.
config.logger.error("Connection error : %s", e) config.logger.info("Raise a RedfishException to upper level")
raise e msg = "Connection error : {}\n".format(e.message)
raise exception.ConnectionFailureException(msg)
except simplejson.scanner.JSONDecodeError as e:
# Log and transmit the exception.
config.logger.info("Raise a RedfishException to upper level")
msg = \
"Ivalid content : Content does not appear to be a valid " + \
"Redfish json\n"
raise exception.InvalidRedfishContentException(msg)
except TypeError as e:
# This happen connecting to a manager using non trusted
# SSL certificats.
# The exception is not what could be expected in such case but this
# is the one provided by Tortilla.
config.logger.info("Raise a RedfishException to upper level")
msg = "Connection error\n"
raise exception.NonTrustedCertificatException(msg)
print self.data print self.data
def get_link_url(self, link_type): def get_link_url(self, link_type):
@ -43,7 +60,7 @@ class Base(object):
""" """
self.links=[] self.links=[]
# Manage standard < 1.0 # Manage standard < 1.0
if float(mapping.redfish_version) < 1.00: if float(mapping.redfish_version) < 1.00:
links = getattr(self.data, mapping.redfish_mapper.map_links()) links = getattr(self.data, mapping.redfish_mapper.map_links())
@ -53,7 +70,7 @@ class Base(object):
links = getattr(self.data, link_type) links = getattr(self.data, link_type)
link = getattr(links, mapping.redfish_mapper.map_links_ref()) link = getattr(links, mapping.redfish_mapper.map_links_ref())
return urljoin(self.url, link) return urljoin(self.url, link)
@property @property
def url(self): def url(self):
return self.__url return self.__url
@ -61,37 +78,37 @@ class Base(object):
@url.setter @url.setter
def url(self, url): def url(self, url):
self.__url = url self.__url = url
def get_parameter(self, parameter_name): def get_parameter(self, parameter_name):
"""Generic function to get any system parameter """Generic function to get any system parameter
:param parameter_name: name of the parameter :param parameter_name: name of the parameter
:returns: string -- parameter value :returns: string -- parameter value
""" """
try: try:
return self.data[parameter_name] return self.data[parameter_name]
except: except:
return "Parameter does not exist" return "Parameter does not exist"
def get_parameters(self): def get_parameters(self):
"""Generic function to get all system parameters """Generic function to get all system parameters
:returns: string -- parameter value :returns: string -- parameter value
""" """
try: try:
return self.data return self.data
except: except:
return -1 return -1
def set_parameter(self, parameter_name, value): def set_parameter(self, parameter_name, value):
"""Generic function to set any system parameter """Generic function to set any system parameter
:param parameter_name: name of the parameter :param parameter_name: name of the parameter
:param value: value to set :param value: value to set
:returns: string -- http response of PATCH request :returns: string -- http response of PATCH request
""" """
# Craft the request # Craft the request
action = dict() action = dict()
@ -103,8 +120,8 @@ class Base(object):
response = self.api_url.patch(verify=self.connection_parameters.verify_cert, response = self.api_url.patch(verify=self.connection_parameters.verify_cert,
headers={'x-auth-token': self.connection_parameters.auth_token}, headers={'x-auth-token': self.connection_parameters.auth_token},
data=action data=action
) )
return response return response
class BaseCollection(Base): class BaseCollection(Base):
"""Abstract class to manage collection (Chassis, Servers etc...).""" """Abstract class to manage collection (Chassis, Servers etc...)."""
@ -112,7 +129,7 @@ class BaseCollection(Base):
super(BaseCollection, self).__init__(url, connection_parameters) super(BaseCollection, self).__init__(url, connection_parameters)
self.links=[] self.links=[]
#linksmembers = self.data.Links.Members #linksmembers = self.data.Links.Members
#linksmembers = self.data.links.Member #linksmembers = self.data.links.Member
@ -143,7 +160,7 @@ class Root(Base):
version = self.data.RedfishVersion version = self.data.RedfishVersion
except AttributeError: except AttributeError:
version = self.data.ServiceVersion version = self.data.ServiceVersion
version = version.replace('.', '') version = version.replace('.', '')
version = version[0] + '.' + version[1:] version = version[0] + '.' + version[1:]
return(version) return(version)
@ -175,7 +192,7 @@ class Managers(Base):
def __init__(self, url, connection_parameters): def __init__(self, url, connection_parameters):
super(Managers, self).__init__(url, connection_parameters) super(Managers, self).__init__(url, connection_parameters)
try: try:
# self.ethernet_interfaces_collection = EthernetInterfacesCollection( # self.ethernet_interfaces_collection = EthernetInterfacesCollection(
# self.get_link_url("EthernetInterfaces"), # self.get_link_url("EthernetInterfaces"),
# connection_parameters # connection_parameters
@ -188,12 +205,12 @@ class Managers(Base):
) )
except: except:
pass pass
def get_firmware_version(self): def get_firmware_version(self):
"""Get bios version of the system. """Get bios version of the system.
:returns: string -- bios version :returns: string -- bios version
""" """
try: try:
# Returned by proliant # Returned by proliant
@ -223,12 +240,12 @@ class Systems(Base):
self.bios = Bios(url + "Bios/Settings", connection_parameters) self.bios = Bios(url + "Bios/Settings", connection_parameters)
except: except:
pass pass
def reset_system(self): def reset_system(self):
"""Force reset of the system. """Force reset of the system.
:returns: string -- http response of POST request :returns: string -- http response of POST request
""" """
# Craft the request # Craft the request
action = dict() action = dict()
@ -243,12 +260,12 @@ class Systems(Base):
) )
#TODO : treat response. #TODO : treat response.
return response return response
def get_bios_version(self): def get_bios_version(self):
"""Get bios version of the system. """Get bios version of the system.
:returns: string -- bios version :returns: string -- bios version
""" """
try: try:
# Returned by proliant # Returned by proliant
@ -262,7 +279,7 @@ class Systems(Base):
"""Get serial number of the system. """Get serial number of the system.
:returns: string -- serial number :returns: string -- serial number
""" """
try: try:
# Returned by proliant # Returned by proliant
@ -271,12 +288,12 @@ class Systems(Base):
# Returned by mockup. # Returned by mockup.
# Hopefully this kind of discrepencies will be fixed with Redfish 1.0 (August) # Hopefully this kind of discrepencies will be fixed with Redfish 1.0 (August)
return "" return ""
def get_power(self): def get_power(self):
"""Get power status of the system. """Get power status of the system.
:returns: string -- power status or NULL if there is an issue :returns: string -- power status or NULL if there is an issue
""" """
try: try:
return self.data.Power return self.data.Power
@ -288,7 +305,7 @@ class Systems(Base):
:param value: json structure with value to update :param value: json structure with value to update
:returns: string -- http response of PATCH request :returns: string -- http response of PATCH request
""" """
# perform the POST action # perform the POST action
#print self.api_url.url() #print self.api_url.url()
@ -297,7 +314,7 @@ class Systems(Base):
headers={'x-auth-token': self.connection_parameters.auth_token, 'Content-type': 'application/json'}, headers={'x-auth-token': self.connection_parameters.auth_token, 'Content-type': 'application/json'},
data=value) data=value)
return response.reason return response.reason
def set_boot_source_override(self, target, enabled): def set_boot_source_override(self, target, enabled):
"""Shotcut function to set boot source """Shotcut function to set boot source
@ -318,16 +335,16 @@ class Systems(Base):
"Once", "Once",
"Continuous" "Continuous"
:returns: string -- http response of PATCH request :returns: string -- http response of PATCH request
""" """
return self.set_parameter_json('{"Boot": {"BootSourceOverrideTarget": "'+target+'"},{"BootSourceOverrideEnabled" : "'+enabled+'"}}') return self.set_parameter_json('{"Boot": {"BootSourceOverrideTarget": "'+target+'"},{"BootSourceOverrideEnabled" : "'+enabled+'"}}')
class SystemsCollection(BaseCollection): class SystemsCollection(BaseCollection):
"""Class to manage redfish ManagersCollection data.""" """Class to manage redfish ManagersCollection data."""
def __init__(self, url, connection_parameters): def __init__(self, url, connection_parameters):
super(SystemsCollection, self).__init__(url, connection_parameters) super(SystemsCollection, self).__init__(url, connection_parameters)
self.systems_list = [] self.systems_list = []
for link in self.links: for link in self.links:
self.systems_list.append(Systems(link, connection_parameters)) self.systems_list.append(Systems(link, connection_parameters))
@ -341,14 +358,14 @@ class Boot(Base):
"""Class to manage redfish Boot data.""" """Class to manage redfish Boot data."""
def __init__(self, url, connection_parameters): def __init__(self, url, connection_parameters):
super(Boot, self).__init__(url, connection_parameters) super(Boot, self).__init__(url, connection_parameters)
class EthernetInterfacesCollection(BaseCollection): class EthernetInterfacesCollection(BaseCollection):
"""Class to manage redfish EthernetInterfacesColkection data.""" """Class to manage redfish EthernetInterfacesColkection data."""
def __init__(self, url, connection_parameters): def __init__(self, url, connection_parameters):
super(EthernetInterfacesCollection, self).__init__(url, connection_parameters) super(EthernetInterfacesCollection, self).__init__(url, connection_parameters)
self.ethernet_interfaces_list = [] self.ethernet_interfaces_list = []
# Url returned by the mock up is wrong /redfish/v1/Managers/EthernetInterfaces/1 returns a 404. # Url returned by the mock up is wrong /redfish/v1/Managers/EthernetInterfaces/1 returns a 404.
# The correct one should be /redfish/v1/Managers/1/EthernetInterfaces/1 # The correct one should be /redfish/v1/Managers/1/EthernetInterfaces/1
# Check more than 1 hour for this bug.... grrr.... # Check more than 1 hour for this bug.... grrr....
@ -357,4 +374,4 @@ class EthernetInterfacesCollection(BaseCollection):
class EthernetInterfaces(Base): class EthernetInterfaces(Base):
"""Class to manage redfish EthernetInterfaces data.""" """Class to manage redfish EthernetInterfaces data."""
pass pass