diff --git a/examples/simple-proliant.py b/examples/simple-proliant.py index a8f5174..468527b 100644 --- a/examples/simple-proliant.py +++ b/examples/simple-proliant.py @@ -6,7 +6,7 @@ import os import sys import json import redfish -from time import sleep + # Get $HOME environment. HOME = os.getenv('HOME') @@ -24,17 +24,26 @@ except IOError as e: print(e) sys.exit(1) -URL = config["Nodes"]["default"]["url"] -USER_NAME = config["Nodes"]["default"]["login"] -PASSWORD = config["Nodes"]["default"]["password"] +URL = config["Managers"]["default"]["url"] +USER_NAME = config["Managers"]["default"]["login"] +PASSWORD = config["Managers"]["default"]["password"] ''' 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()) # 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 # and avoid systems_list[0] diff --git a/examples/simple-simulator.py b/examples/simple-simulator.py index 52d9f18..b9db1af 100644 --- a/examples/simple-simulator.py +++ b/examples/simple-simulator.py @@ -23,13 +23,22 @@ except IOError as e: print(e) sys.exit(1) -URL = config["Nodes"]["default"]["url"] -USER_NAME = config["Nodes"]["default"]["login"] -PASSWORD = config["Nodes"]["default"]["password"] +URL = config["Managers"]["default"]["url"] +USER_NAME = config["Managers"]["default"]["login"] +PASSWORD = config["Managers"]["default"]["password"] ''' remoteMgmt is a redfish.RedfishConnection object ''' -remote_mgmt = redfish.connect(URL, USER_NAME, PASSWORD, - simulator=True, enforceSSL=False) +try: + 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("UUID : {} \n".format(remote_mgmt.Root.get_api_UUID())) diff --git a/redfish/exception.py b/redfish/exception.py index 6038480..5152f80 100644 --- a/redfish/exception.py +++ b/redfish/exception.py @@ -1,21 +1,52 @@ # -*- coding: utf-8 -*- -import sys + import config + class RedfishException(Exception): """Base class for redfish exceptions""" - def __init__(self, message=None, **kwargs): + def __init__(self, message, **kwargs): self.kwargs = kwargs 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): - def __init__(self, message=None, **kwargs): - super(AuthenticationFailureException, self).__init__(message=None, **kwargs) - config.logger.error(message) - # TODO - # Give a bit more details about the failure (check login etc...) - sys.exit(1) - + def __init__(self, message, **kwargs): + super(AuthenticationFailureException, self).__init__(message, **kwargs) + self.message += str(kwargs['code']) + self.queryAnswer = kwargs['queryAnswer'] + if kwargs['code'] == 400: + self.message += ': ' + self.queryAnswer['Messages'][0]['MessageID'] + self.advices = '1- Check your credentials\n' + self.message += '\n' + + class LogoutFailureException(RedfishException): pass diff --git a/redfish/main.py b/redfish/main.py index fbe9f21..6012a20 100644 --- a/redfish/main.py +++ b/redfish/main.py @@ -271,7 +271,7 @@ class RedfishConnection(object): # # print self.systemCollection.Name # - # ======================================================================== + # ======================================================================== def get_api_version(self): """Return api version. @@ -286,14 +286,15 @@ class RedfishConnection(object): url = self.Root.get_link_url( 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 if float(mapping.redfish_version) >= 1.00: url += '/Sessions' # Craft request body and header requestBody = {"UserName": self.connection_parameters.user_name , "Password": self.connection_parameters.password} + config.logger.debug(requestBody) header = {'Content-type': 'application/json'} # ======================================================================= # Tortilla seems not able to provide the header of a post request answer. @@ -308,13 +309,16 @@ class RedfishConnection(object): headers=header, verify=self.connection_parameters.verify_cert ) - + # ======================================================================= # TODO : Manage exception with a class. # ======================================================================= if auth.status_code != 201: - raise exception.AuthenticationFailureException("Login request return an invalid status code") - #sysraise "Error getting token", auth.status_code + try: + 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.user_uri = auth.headers.get("location") diff --git a/redfish/types.py b/redfish/types.py index d6b3717..8423ff9 100644 --- a/redfish/types.py +++ b/redfish/types.py @@ -1,12 +1,14 @@ # coding=utf-8 import pprint +import re from urlparse import urljoin import requests +import simplejson import tortilla import config import mapping -import re +import exception # Global variable @@ -28,10 +30,25 @@ class Base(object): headers={'x-auth-token': connection_parameters.auth_token} ) except requests.ConnectionError as e: - print e # Log and transmit the exception. - config.logger.error("Connection error : %s", e) - raise e + config.logger.info("Raise a RedfishException to upper level") + 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 def get_link_url(self, link_type): @@ -43,7 +60,7 @@ class Base(object): """ self.links=[] - + # Manage standard < 1.0 if float(mapping.redfish_version) < 1.00: links = getattr(self.data, mapping.redfish_mapper.map_links()) @@ -53,7 +70,7 @@ class Base(object): links = getattr(self.data, link_type) link = getattr(links, mapping.redfish_mapper.map_links_ref()) return urljoin(self.url, link) - + @property def url(self): return self.__url @@ -61,37 +78,37 @@ class Base(object): @url.setter def url(self, url): self.__url = url - + def get_parameter(self, parameter_name): """Generic function to get any system parameter :param parameter_name: name of the parameter :returns: string -- parameter value - + """ try: return self.data[parameter_name] except: return "Parameter does not exist" - + def get_parameters(self): """Generic function to get all system parameters :returns: string -- parameter value - + """ try: return self.data except: return -1 - + def set_parameter(self, parameter_name, value): """Generic function to set any system parameter :param parameter_name: name of the parameter :param value: value to set :returns: string -- http response of PATCH request - + """ # Craft the request action = dict() @@ -103,8 +120,8 @@ class Base(object): response = self.api_url.patch(verify=self.connection_parameters.verify_cert, headers={'x-auth-token': self.connection_parameters.auth_token}, data=action - ) - return response + ) + return response class BaseCollection(Base): """Abstract class to manage collection (Chassis, Servers etc...).""" @@ -112,7 +129,7 @@ class BaseCollection(Base): super(BaseCollection, self).__init__(url, connection_parameters) self.links=[] - + #linksmembers = self.data.Links.Members #linksmembers = self.data.links.Member @@ -143,7 +160,7 @@ class Root(Base): version = self.data.RedfishVersion except AttributeError: version = self.data.ServiceVersion - + version = version.replace('.', '') version = version[0] + '.' + version[1:] return(version) @@ -175,7 +192,7 @@ class Managers(Base): def __init__(self, url, connection_parameters): super(Managers, self).__init__(url, connection_parameters) try: - + # self.ethernet_interfaces_collection = EthernetInterfacesCollection( # self.get_link_url("EthernetInterfaces"), # connection_parameters @@ -188,12 +205,12 @@ class Managers(Base): ) except: pass - + def get_firmware_version(self): """Get bios version of the system. :returns: string -- bios version - + """ try: # Returned by proliant @@ -223,12 +240,12 @@ class Systems(Base): self.bios = Bios(url + "Bios/Settings", connection_parameters) except: pass - + def reset_system(self): """Force reset of the system. :returns: string -- http response of POST request - + """ # Craft the request action = dict() @@ -243,12 +260,12 @@ class Systems(Base): ) #TODO : treat response. return response - + def get_bios_version(self): """Get bios version of the system. :returns: string -- bios version - + """ try: # Returned by proliant @@ -262,7 +279,7 @@ class Systems(Base): """Get serial number of the system. :returns: string -- serial number - + """ try: # Returned by proliant @@ -271,12 +288,12 @@ class Systems(Base): # Returned by mockup. # Hopefully this kind of discrepencies will be fixed with Redfish 1.0 (August) return "" - + def get_power(self): """Get power status of the system. :returns: string -- power status or NULL if there is an issue - + """ try: return self.data.Power @@ -288,7 +305,7 @@ class Systems(Base): :param value: json structure with value to update :returns: string -- http response of PATCH request - + """ # perform the POST action #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'}, data=value) return response.reason - + def set_boot_source_override(self, target, enabled): """Shotcut function to set boot source @@ -318,16 +335,16 @@ class Systems(Base): "Once", "Continuous" :returns: string -- http response of PATCH request - """ + """ return self.set_parameter_json('{"Boot": {"BootSourceOverrideTarget": "'+target+'"},{"BootSourceOverrideEnabled" : "'+enabled+'"}}') class SystemsCollection(BaseCollection): """Class to manage redfish ManagersCollection data.""" def __init__(self, url, connection_parameters): super(SystemsCollection, self).__init__(url, connection_parameters) - + self.systems_list = [] - + for link in self.links: self.systems_list.append(Systems(link, connection_parameters)) @@ -341,14 +358,14 @@ class Boot(Base): """Class to manage redfish Boot data.""" def __init__(self, url, connection_parameters): super(Boot, self).__init__(url, connection_parameters) - + class EthernetInterfacesCollection(BaseCollection): """Class to manage redfish EthernetInterfacesColkection data.""" def __init__(self, url, connection_parameters): super(EthernetInterfacesCollection, self).__init__(url, connection_parameters) - + self.ethernet_interfaces_list = [] - + # 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 # Check more than 1 hour for this bug.... grrr.... @@ -357,4 +374,4 @@ class EthernetInterfacesCollection(BaseCollection): class EthernetInterfaces(Base): """Class to manage redfish EthernetInterfaces data.""" - pass \ No newline at end of file + pass