Merge pull request #11 from uggla/devel

My modifications for future version 0.3
This commit is contained in:
Bruno Cornec 2016-01-18 16:29:52 +01:00
commit df2c027823
12 changed files with 826 additions and 248 deletions

View File

@ -1,6 +1,7 @@
#!/bin/bash
function start_apache {
[ -f "/run/apache2/apache2.pid" ] && rm "/run/apache2/apache2.pid"
echo "Launching apache2 in foreground with /usr/sbin/apache2ctl -DFOREGROUND -k start"
/usr/sbin/apache2ctl -DFOREGROUND -k start
}

View File

@ -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]

View File

@ -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()))

View File

@ -2,15 +2,16 @@
# coding=utf-8
"""
'''
redfish-client
Usage:
redfish-client.py [options] config add <manager_name> <manager_url> [<login>] [<password>]
redfish-client.py [options] config del <manager_name>
redfish-client.py [options] config modify <manager_name> (url | login | password) <changed_value>
redfish-client.py [options] config modify <manager_name> (manager_name | url | login | password) <changed_value>
redfish-client.py [options] config show
redfish-client.py [options] config showall
redfish-client.py [options] manager getinfo [<manager_name>]
redfish-client.py (-h | --help)
redfish-client.py --version
@ -18,22 +19,43 @@ Usage:
Options:
-h --help Show this screen.
--version Show version.
--conf_file FILE Configuration file [default: ~/.redfish.conf].
--conf_file FILE Configuration file [default: ~/.redfish.conf]
--insecure Check SSL certificats
--debug LEVEL Run in debug mode, LEVEL from 1 to 3 increase verbosity
Security warning LEVEL > 1 could reveal password into the logs
--debugfile FILE Specify the client debugfile [default: redfish-client.log]
--libdebugfile FILE Specify python-redfish library log file [default: /var/log/python-redfish/python-redfish.log]
config commands manage the configuration file.
"""
config commands : manage the configuration file.
manager commands : manage the manager (Ligh out management). If <manager_name>
is not provided use the 'default' entry
'''
import os
import sys
import json
import pprint
import docopt
import logging
import redfish
import requests.packages.urllib3
import jinja2
class ConfigFile(object):
'''redfisht-client configuration file management'''
def __init__(self, config_file):
'''Initialize the configuration file
Open and load configuration file data.
If the file does not exist create an empty one ready to receive data
:param config_file: File name of the configuration file
default: ~/.redfish.conf
:type str
:returns: Nothing
'''
self._config_file = config_file
# read json file
try:
@ -41,86 +63,330 @@ class ConfigFile(object):
self.data = json.load(json_data)
json_data.close()
except (ValueError, IOError):
self.data = {"Managers":{}}
self.data = {'Managers': {}}
def save(self):
'''Save the configuration file data'''
try:
with open(self._config_file , 'w') as json_data:
with open(self._config_file, 'w') as json_data:
json.dump(self.data, json_data)
json_data.close()
except IOError as e:
print(e.msg)
sys.exit(1)
def manager_incorect(self, exception):
''' Log and exit if manager name is incorect'''
logger.error('Incorect manager name : %s' % exception.args)
sys.exit(1)
def check_manager(self, manager_name):
'''Check if the manager exists in configuration file
:param manager_name: Name of the manager
:type str
'''
try:
if manager_name not in self.get_managers():
raise KeyError(manager_name)
except KeyError as e:
self.manager_incorect(e)
def add_manager(self, manager_name, url, login, password):
'''Add a manager to the configuration file
:param manager_name: Name of the manager
:type str
:param url: Url of the manager
:type str
:param login: Login of the manager
:type str
:param password: Password of the manager
:type str
'''
self.data['Managers'][manager_name] = {}
self.data['Managers'][manager_name]['url'] = url
if login != None:
if login is None:
login = ''
if password is None:
password = ''
self.data['Managers'][manager_name]['login'] = login
if password != None:
self.data['Managers'][manager_name]['password'] = password
def modify_manager(self, manager_name, parameter, parameter_value):
'''Modify the manager settings
:param manager name: Name of the manager
:type str
:param parameter: url | login | password
:type str
:param parameter_value: Value of the parameter
:type str
:returns: Nothing
'''
if parameter == 'url':
try:
self.data['Managers'][manager_name]['url'] = parameter_value
except KeyError as e:
self.manager_incorect(e)
elif parameter == 'login':
try:
self.data['Managers'][manager_name]['login'] = parameter_value
except KeyError as e:
self.manager_incorect(e)
elif parameter == 'password':
try:
self.data['Managers'][manager_name]['password'] = parameter_value
except KeyError as e:
self.manager_incorect(e)
elif parameter == 'manager_name':
# Create a new entry with the new name
self.add_manager(parameter_value,
self.data['Managers'][manager_name]['url'],
self.data['Managers'][manager_name]['login'],
self.data['Managers'][manager_name]['password'],
)
# Remove the previous one
self.delete_manager(manager_name)
def delete_manager(self, manager_name):
'''Delete manager
:param manager name: Name of the manager
:type str
:returns: Nothing
'''
try:
del self.data['Managers'][manager_name]
except KeyError as e:
self.manager_incorect(e)
def get_managers(self):
'''Get manager configured
:returns: Managers
:type list
'''
managers = []
for manager in self.data['Managers']:
managers += [manager]
return(managers)
def get_manager_info(self, manager):
'''Show manager info (url, login, password)
:param manager: Name of the manager
:type str
:returns: info containing url, login, password
:type dict
'''
info = {}
url=self.data['Managers'][manager]['url']
login=self.data['Managers'][manager]['login']
password=self.data['Managers'][manager]['password']
info={'url':url, 'login':login, 'password':password}
url = self.data['Managers'][manager]['url']
login = self.data['Managers'][manager]['login']
password = self.data['Managers'][manager]['password']
info = {'url': url, 'login': login, 'password': password}
return(info)
class RedfishClientException(Exception):
"""Base class for redfish client exceptions"""
'''Base class for redfish client exceptions'''
def __init__(self, message=None, **kwargs):
self.kwargs = kwargs
self.message = message
if __name__ == '__main__':
'''Main application redfish-client'''
# Functions
def show_manager(all=False):
print("Managers configured :")
'''Display manager info
:param all: Add login and password info
:type bool
:returns: Nothing
'''
print('Managers configured :')
for manager in conf_file.get_managers():
print(manager)
if all == True:
if all is True:
info = conf_file.get_manager_info(manager)
print("\tUrl : {}".format(info['url']))
print("\tLogin : {}".format(info['login']))
print("\tPassword : {}".format(info['password']))
print('\tUrl : {}'.format(info['url']))
print('\tLogin : {}'.format(info['login']))
print('\tPassword : {}'.format(info['password']))
def get_manager_info(manager_name, check_SSL):
connection_parameters = conf_file.get_manager_info(manager_name)
if not connection_parameters['login']:
simulator = True
enforceSSL = False
else:
simulator = False
enforceSSL = True
try:
print('Gathering data from manager, please wait...\n')
# TODO : Add a rotating star showing program is running ?
# Could be a nice exercice for learning python. :)
logger.info('Gathering data from manager')
remote_mgmt = redfish.connect(connection_parameters['url'],
connection_parameters['login'],
connection_parameters['password'],
verify_cert=check_SSL,
simulator=simulator,
enforceSSL=enforceSSL
)
except redfish.exception.RedfishException as e:
sys.stderr.write(str(e.message))
sys.stderr.write(str(e.advices))
sys.exit(1)
# Display manager information using jinja2 template
template = jinja2_env.get_template("manager_info.template")
print template.render(r=remote_mgmt)
# Main program
redfishclient_version = "redfish-client 0.1"
# Parse and manage arguments
arguments = docopt.docopt(__doc__, version=redfishclient_version)
# Check debuging options
# Debugging LEVEL :
# 1- Only client
# 2- Client and lib
# 3- Client and lib + Tortilla
loglevel = {"console_logger_level": "nolog",
"file_logger_level": logging.INFO,
"tortilla": False,
"lib_console_logger_level": "nolog",
"lib_file_logger_level": logging.INFO,
"urllib3_disable_warning": True}
if arguments['--debug'] == '1':
loglevel['console_logger_level'] = logging.DEBUG
loglevel['file_logger_level'] = logging.DEBUG
elif arguments['--debug'] == '2':
loglevel['console_logger_level'] = logging.DEBUG
loglevel['file_logger_level'] = logging.DEBUG
loglevel['lib_console_logger_level'] = logging.DEBUG
loglevel['lib_file_logger_level'] = logging.DEBUG
loglevel['urllib3_disable_warning'] = False
elif arguments['--debug'] == '3':
loglevel['console_logger_level'] = logging.DEBUG
loglevel['file_logger_level'] = logging.DEBUG
loglevel['lib_console_logger_level'] = logging.DEBUG
loglevel['lib_file_logger_level'] = logging.DEBUG
loglevel['urllib3_disable_warning'] = False
loglevel['tortilla'] = True
# Initialize logger according to command line parameters
logger = redfish.config.initialize_logger(arguments['--debugfile'],
loglevel['console_logger_level'],
loglevel['file_logger_level'],
__name__)
redfish.config.REDFISH_LOGFILE = arguments['--libdebugfile']
redfish.config.TORTILLADEBUG = loglevel['tortilla']
redfish.config.CONSOLE_LOGGER_LEVEL = loglevel['lib_console_logger_level']
redfish.config.FILE_LOGGER_LEVEL = loglevel['lib_file_logger_level']
# Avoid warning messages from request / urllib3
# SecurityWarning: Certificate has no `subjectAltName`, falling back
# to check for a `commonName` for now. This feature is being removed
# by major browsers and deprecated by RFC 2818.
# (See https://github.com/shazow/urllib3/issues/497 for details.)
if loglevel['urllib3_disable_warning'] is True:
requests.packages.urllib3.disable_warnings()
logger.info("*** Starting %s ***" % redfishclient_version)
logger.info("Arguments parsed")
logger.debug(arguments)
# Get $HOME environment.
HOME = os.getenv('HOME')
if HOME == '':
print("$HOME environment variable not set, please check your system")
if not HOME:
print('$HOME environment variable not set, please check your system')
logger.error('$HOME environment variable not set')
sys.exit(1)
arguments = docopt.docopt(__doc__, version='redfish-client 0.1')
print(arguments)
logger.debug("Home directory : %s" % HOME)
arguments['--conf_file'] = arguments['--conf_file'].replace('~', HOME)
conf_file = ConfigFile(arguments['--conf_file'])
# Initialize Template system (jinja2)
# TODO : set the template file location into cmd line default to /usr/share/python-redfish/templates ?
logger.debug("Initialize template system")
jinja2_env = jinja2.Environment(loader=jinja2.FileSystemLoader("templates"))
if arguments['config'] == True:
if arguments['show'] == True:
if arguments['config'] is True:
logger.debug("Config commands")
if arguments['show'] is True:
logger.debug('show command')
show_manager()
elif arguments['showall'] == True:
elif arguments['showall'] is True:
logger.debug('showall command')
show_manager(True)
elif arguments['add'] == True:
elif arguments['add'] is True:
logger.debug('add command')
conf_file.add_manager(arguments['<manager_name>'],
arguments['<manager_url>'],
arguments['<login>'],
arguments['password'])
pprint.pprint(conf_file.data)
arguments['<password>'])
logger.debug(conf_file.data)
conf_file.save()
elif arguments['del'] is True:
logger.debug('del command')
conf_file.delete_manager(arguments['<manager_name>'])
logger.debug(conf_file.data)
conf_file.save()
elif arguments['modify'] is True:
logger.debug('modify command')
if arguments['url'] is not False:
conf_file.modify_manager(arguments['<manager_name>'],
'url',
arguments['<changed_value>'])
elif arguments['login'] is not False:
conf_file.modify_manager(arguments['<manager_name>'],
'login',
arguments['<changed_value>'])
elif arguments['password'] is not False:
conf_file.modify_manager(arguments['<manager_name>'],
'password',
arguments['<changed_value>'])
elif arguments['manager_name'] is not False:
conf_file.modify_manager(arguments['<manager_name>'],
'manager_name',
arguments['<changed_value>'])
logger.debug(conf_file.data)
conf_file.save()
if arguments['manager'] is True:
logger.debug("Manager commands")
if arguments['getinfo'] is True:
logger.debug('getinfo command')
# If manager is not defined set it to 'default'
if not arguments['<manager_name>']:
manager_name = 'default'
else:
manager_name = arguments['<manager_name>']
# Check if the default section is available in our conf file
conf_file.check_manager(manager_name)
if arguments['--insecure'] is True:
get_manager_info(manager_name, False)
else:
get_manager_info(manager_name, True)
logger.info("Client session teminated")
sys.exit(0)

View File

@ -0,0 +1,32 @@
#=======================================================================
# print('Redfish API version : %s' % remote_mgmt.get_api_version())
# print(remote_mgmt.Root.get_name())
# print('\n')
# print('Managers information :')
# print('----------------------')
# for manager_index in sorted(remote_mgmt.Managers.managers_dict):
# manager = remote_mgmt.Managers.managers_dict[manager_index]
# print('\nManager id {} :').format(manager_index)
# print('UUID : {}').format(manager.get_uuid())
# print('Type : {}').format(manager.get_type())
# print('Firmware version : {}').format(manager.get_firmware_version())
# print('State : {}').format(manager.get_status())
# print manager.get_managed_chassis()
# print manager.get_managed_systems()
# print('Ethernet interfaces :')
# try :
# for ethernetinterface_index in sorted(manager.ethernet_interfaces_collection.ethernet_interfaces_dict):
# ei = manager.ethernet_interfaces_collection.ethernet_interfaces_dict[ethernetinterface_index]
# print('\nEthernet Interface id {} :').format(ethernetinterface_index)
# print(ei.get_name())
# print(ei.get_parameter('FQDN'))
# print ei.get_ipv4()
# print ei.get_ipv6()
# except AttributeError:
# # We don't have ethernet interfaces
# pass
#=======================================================================
Redfish API version : remote_mgmt.get_api_version()
remote_mgmt.Root.get_name()

View File

@ -0,0 +1,31 @@
Redfish API version : {{ r.get_api_version() }}
{{ r.Root.get_name() }}
Managers information :
======================
{% for manager_index in r.Managers.managers_dict | sort %}
{%- set manager = r.Managers.managers_dict[manager_index] %}
Manager id {{ manager_index }}:
UUID : {{ manager.get_uuid() }}
Type : {{ manager.get_type() }}
Firmware version : {{ manager.get_firmware_version() }}
State : {{ manager.get_status() }}
Ethernet Interface :
{%- if manager.ethernet_interfaces_collection %}
{%- for ethernetinterface_index in manager.ethernet_interfaces_collection.ethernet_interfaces_dict | sort %}
{%- set ei = manager.ethernet_interfaces_collection.ethernet_interfaces_dict[ethernetinterface_index] %}
Ethernet Interface id {{ ethernetinterface_index }} :
{{ ei.get_name() }}
FQDN : {{ ei.get_fqdn() }}
Address ipv4 : {{ ei.get_ipv4() | join(', ') }}
Address ipv6 : {{ ei.get_ipv6() | join(', ') }}
{%- endfor %}
{%- else %}
This manager has no ethernet interface
{%- endif %}
Managed Chassis :
{{ manager.get_managed_chassis() | join(', ') }}
Managed System :
{{ manager.get_managed_systems() | join(', ') }}
----------------------------
{% endfor %}

View File

@ -4,34 +4,46 @@ import logging
from logging.handlers import RotatingFileHandler
# Global variable definition
TORTILLADEBUG = True
logger = None
TORTILLADEBUG = True
REDFISH_LOGFILE = "/var/log/python-redfish/python-redfish.log"
CONSOLE_LOGGER_LEVEL = logging.DEBUG
FILE_LOGGER_LEVEL = logging.DEBUG
def initialize_logger(redfish_logfile):
"""Return api version.
def initialize_logger(REDFISH_LOGFILE,
CONSOLE_LOGGER_LEVEL,
FILE_LOGGER_LEVEL,
logger_name=None):
'''Initialize a global logger to track application behaviour
:param redfish_logfile: redfish log
:param redfish_logfile: Log filename
:type str
:returns: True
:param screen_logger_level: Console log level
(logging.DEBUG, logging.ERROR, ..) or nolog
:type logging constant or string
:param file_logger_level: File log level
:type logging constant
:returns: logging object
"""
global logger
logger = logging.getLogger()
'''
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s :: %(levelname)s :: %(message)s'
)
file_handler = RotatingFileHandler(redfish_logfile, 'a', 1000000, 1)
file_handler = RotatingFileHandler(REDFISH_LOGFILE, 'a', 1000000, 1)
# First logger to file
file_handler.setLevel(logging.DEBUG)
file_handler.setLevel(FILE_LOGGER_LEVEL)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# Second logger to console
if CONSOLE_LOGGER_LEVEL != "nolog":
steam_handler = logging.StreamHandler()
steam_handler.setLevel(logging.DEBUG)
steam_handler.setLevel(CONSOLE_LOGGER_LEVEL)
logger.addHandler(steam_handler)
return True
return logger

View File

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

View File

@ -117,7 +117,7 @@ Clients should always be prepared for:
# coding=utf-8
import sys
import json
from urlparse import urlparse
import requests
@ -126,21 +126,7 @@ import types
import mapping
import exception
# Global variable definition
redfish_logfile = "/var/log/python-redfish/python-redfish.log"
# ===============================================================================
# TODO : create method to set logging level and TORTILLADEBUG.
# ===============================================================================
def set_log_file(logfile):
global redfish_logfile
redfish_logfile = logfile
return True
""" Function to wrap RedfishConnection """
"""Function to wrap RedfishConnection"""
def connect(
@ -151,8 +137,7 @@ def connect(
enforceSSL=True,
verify_cert=True
):
global redfish_logfile
config.initialize_logger(redfish_logfile)
return RedfishConnection(
url,
user,
@ -175,7 +160,14 @@ class RedfishConnection(object):
verify_cert=True
):
"""Initialize a connection to a Redfish service."""
super(RedfishConnection, self).__init__()
# Specify a name for the logger as recommended by the logging
# documentation. However for strange reason requests logs are not
# anymore capture in the log file.
# TODO : Check strange behavior about requests logs.
config.logger = config.initialize_logger(config.REDFISH_LOGFILE,
config.CONSOLE_LOGGER_LEVEL,
config.FILE_LOGGER_LEVEL,
__name__)
config.logger.info("Initialize python-redfish")
@ -217,9 +209,10 @@ class RedfishConnection(object):
config.logger.info("API Version : %s", self.get_api_version())
mapping.redfish_version = self.get_api_version()
mapping.redfish_root_name = self.Root.get_name()
# Instanciate a global mapping object to handle Redfish version variation
mapping.redfish_mapper = mapping.RedfishVersionMapping(self.get_api_version())
# Instantiate a global mapping object to handle Redfish version variation
mapping.redfish_mapper = mapping.RedfishVersionMapping(self.get_api_version(), self.Root.get_name())
# Now we need to login otherwise we are not allowed to extract data
if self.__simulator is False:
@ -233,7 +226,7 @@ class RedfishConnection(object):
# Struture change with mockup 1.0.0, there is no links
# Structure change with mockup 1.0.0, there is no links
# section anymore.
# ===================================================================
# TODO : Add a switch to allow the both structure
@ -294,6 +287,7 @@ class RedfishConnection(object):
# 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.
@ -313,8 +307,11 @@ class RedfishConnection(object):
# 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")

View File

@ -2,30 +2,51 @@
redfish_mapper = None
redfish_version = None
redfish_root_name = None
class RedfishVersionMapping(object):
"""Implements basic url path mapping beetween Redfish versions."""
'''Implements basic url path mapping beetween Redfish versions.'''
def __init__(self, version):
def __init__(self, version, rootname):
self.__version = version
self.__rootname = rootname
def map_sessionservice(self):
if self.__version == "0.95":
return "Sessions"
return("SessionService")
if self.__version == '0.95':
return 'Sessions'
return 'SessionService'
def map_links(self, data_dict=None):
if data_dict == None:
if self.__version == '0.95':
return 'links'
else:
# Checking if we have Links or links.
# This is to deal with proliant firmware 2.40 bug that reports
# incorrectly links instead of Links (Redfish standard)
try:
data_dict.links
return 'links'
except AttributeError:
pass
return 'Links'
def map_links(self):
if self.__version == "0.95":
return "links"
return("Links")
def map_links_ref(self):
if self.__version == "0.95":
return "href"
return("@odata.id")
def map_links_ref(self, data_dict=None):
if data_dict == None:
if self.__version == '0.95':
return 'href'
else:
# Checking if we have @odata.id or href.
# This is to deal with proliant firmware 2.40 bug that reports
# incorrectly href instead of @odata.id (Redfish standard)
try:
data_dict.href
return 'href'
except AttributeError:
pass
return '@odata.id'
def map_members(self):
if self.__version == "0.95":
return "Member"
return("Members")
if self.__version == '0.95':
return 'Member'
return 'Members'

View File

@ -1,20 +1,22 @@
# 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
class Base(object):
"""Abstract class to manage types (Chassis, Servers etc...)."""
'''Abstract class to manage types (Chassis, Servers etc...).'''
def __init__(self, url, connection_parameters):
"""Class constructor"""
'''Class constructor'''
global TORTILLADEBUG
self.connection_parameters = connection_parameters # Uggly hack to check
self.url = url
@ -28,20 +30,35 @@ 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
print self.data
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)
config.logger.debug(self.data)
def get_link_url(self, link_type):
"""Need to be explained.
'''Need to be explained.
:param redfish_logfile: redfish log
:type str
:returns: True
"""
'''
self.links=[]
# Manage standard < 1.0
@ -63,51 +80,63 @@ class Base(object):
self.__url = url
def get_parameter(self, parameter_name):
"""Generic function to get any system parameter
'''Generic function to get a specific parameter
:param parameter_name: name of the parameter
:returns: string -- parameter value
"""
'''
try:
return self.data[parameter_name]
except:
return "Parameter does not exist"
return 'Parameter does not exist'
def get_parameters(self):
"""Generic function to get all system parameters
'''Generic function to get all 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
'''Generic function to set a specific 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()
action[parameter_name] = value
print(action)
config.logger.debug(action)
# Perform the POST action
print self.api_url
response = self.api_url.patch(verify=self.connection_parameters.verify_cert,
config.logger.debug(self.api_url)
response = self.api_url.patch(
verify=self.connection_parameters.verify_cert,
headers={'x-auth-token': self.connection_parameters.auth_token},
data=action
)
data=action)
return response
def get_name(self):
'''Get root name
:returns: string -- root name or "Not available"
'''
try:
return self.data.Name
except AttributeError:
return "Not available"
class BaseCollection(Base):
"""Abstract class to manage collection (Chassis, Servers etc...)."""
'''Abstract class to manage collection (Chassis, Servers etc...).'''
def __init__(self, url, connection_parameters):
super(BaseCollection, self).__init__(url, connection_parameters)
@ -122,23 +151,23 @@ class BaseCollection(Base):
else:
linksmembers = getattr(self.data, mapping.redfish_mapper.map_members())
for link in linksmembers:
#self.links.append(getattr(link,"@odata.id"))
#self.links.append(getattr(link,"href"))
#self.links.append(getattr(link,'@odata.id'))
#self.links.append(getattr(link,'href'))
self.links.append(urljoin(self.url, getattr(link, mapping.redfish_mapper.map_links_ref())))
print self.links
config.logger.debug(self.links)
class Root(Base):
"""Class to manage redfish Root data."""
'''Class to manage redfish Root data.'''
def get_api_version(self):
"""Return api version.
'''Return api version.
:returns: string -- version
:raises: AttributeError
"""
'''
try:
version = self.data.RedfishVersion
except AttributeError:
@ -149,87 +178,162 @@ class Root(Base):
return(version)
def get_api_UUID(self):
"""Return UUID version.
'''Return UUID version.
:returns: string -- UUID
"""
'''
return self.data.UUID
def get_api_link_to_server(self):
"""Return api link to server.
'''Return api link to server.
:returns: string -- path
"""
return getattr(self.root.Links.Systems, "@odata.id")
'''
return getattr(self.root.Links.Systems, '@odata.id')
class SessionService(Base):
"""Class to manage redfish SessionService data."""
'''Class to manage redfish SessionService data.'''
pass
class Managers(Base):
"""Class to manage redfish Managers."""
'''Class to manage redfish Managers.'''
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
# )
# Works on proliant, need to treat 095 vs 0.96 differences
# New proliant firmware now respects Redfish v1.00, so seems to correct below statement
# TODO : better handle exception and if possible support old firmware ?
self.ethernet_interfaces_collection = EthernetInterfacesCollection(
self.get_link_url("EthernetNICs"),
self.get_link_url('EthernetInterfaces'),
connection_parameters
)
except:
pass
# Works on proliant, need to treat 095 vs 0.96 differences
#self.ethernet_interfaces_collection = EthernetInterfacesCollection(
# self.get_link_url('EthernetNICs'),
# connection_parameters
# )
except exception.InvalidRedfishContentException:
# This is to avoid invalid content from the mockup
self.ethernet_interfaces_collection = None
except AttributeError:
# This means we don't have EthernetInterfaces
self.ethernet_interfaces_collection = None
def get_firmware_version(self):
"""Get bios version of the system.
'''Get firmware version of the manager
:returns: string -- bios version
:returns: string -- bios version or "Not available"
"""
'''
try:
# Returned by proliant
return self.data.FirmwareVersion
except:
# Returned by mockup.
# Hopefully this kind of discrepencies will be fixed with Redfish 1.0 (August)
return self.data.FirmwareVersion
except AttributeError:
# We are here because the attribute could be not defined.
# This is the case with the mockup for manager 2 and 3
return "Not available"
def get_type(self):
'''Get manager type
:returns: string -- manager type or "Not available"
'''
try:
return self.data.ManagerType
except AttributeError:
return "Not available"
def get_uuid(self):
'''Get manager type
:returns: string -- manager uuid or "Not available"
'''
try:
return self.data.UUID
except AttributeError:
return "Not available"
def get_status(self):
'''Get manager status
:returns: string -- manager status or "Not available"
'''
try:
return self.data.Status.State
except AttributeError:
return "Not available"
def get_managed_chassis(self):
'''Get managed chassis ids by the manager
:returns: list -- chassis ids or "Not available"
'''
chassis_list = []
links = getattr(self.data, mapping.redfish_mapper.map_links(self.data))
try:
for chassis in links.ManagerForChassis:
result = re.search(r'Chassis/(\w+)', chassis[mapping.redfish_mapper.map_links_ref(chassis)])
chassis_list.append(result.group(1))
return chassis_list
except AttributeError:
return "Not available"
def get_managed_systems(self):
'''Get managed systems ids by the manager
:returns: list -- chassis ids or "Not available"
'''
systems_list = []
links = getattr(self.data, mapping.redfish_mapper.map_links(self.data))
try:
for systems in links.ManagerForServers:
result = re.search(r'Systems/(\w+)', systems[mapping.redfish_mapper.map_links_ref(systems)])
systems_list.append(result.group(1))
return systems_list
except AttributeError:
return "Not available"
class ManagersCollection(BaseCollection):
"""Class to manage redfish ManagersCollection data."""
'''Class to manage redfish ManagersCollection data.'''
def __init__(self, url, connection_parameters):
"""Class constructor"""
'''Class constructor'''
super(ManagersCollection, self).__init__(url, connection_parameters)
self.managers_list = []
self.managers_dict = {}
for link in self.links:
self.managers_list.append(Managers(link, connection_parameters))
index = re.search(r'Managers/(\w+)', link)
self.managers_dict[index.group(1)] = Managers(link, connection_parameters)
class Systems(Base):
"""Class to manage redfish Systems data."""
'''Class to manage redfish Systems data.'''
# TODO : Need to discuss with Bruno the required method.
# Also to check with the ironic driver requirement.
def __init__(self, url, connection_parameters):
"""Class constructor"""
'''Class constructor'''
super(Systems, self).__init__(url, connection_parameters)
try:
self.bios = Bios(url + "Bios/Settings", connection_parameters)
self.bios = Bios(url + 'Bios/Settings', connection_parameters)
except:
pass
def reset_system(self):
"""Force reset of the system.
'''Force reset of the system.
:returns: string -- http response of POST request
"""
'''
# Craft the request
action = dict()
action['Action'] = 'Reset'
@ -245,11 +349,11 @@ class Systems(Base):
return response
def get_bios_version(self):
"""Get bios version of the system.
'''Get bios version of the system.
:returns: string -- bios version
"""
'''
try:
# Returned by proliant
return self.data.Bios.Current.VersionString
@ -259,37 +363,37 @@ class Systems(Base):
return self.data.BiosVersion
def get_serial_number(self):
"""Get serial number of the system.
'''Get serial number of the system.
:returns: string -- serial number
"""
'''
try:
# Returned by proliant
return self.data.SerialNumber
except:
# Returned by mockup.
# Hopefully this kind of discrepencies will be fixed with Redfish 1.0 (August)
return ""
return ''
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
"""
'''
try:
return self.data.Power
except:
return ""
return ''
def set_parameter_json(self, value):
"""Generic function to set any system parameter using json structure
'''Generic function to set any system parameter using json structure
:param value: json structure with value to update
:returns: string -- http response of PATCH request
"""
'''
# perform the POST action
#print self.api_url.url()
response = requests.patch(self.api_url.url(),
@ -299,7 +403,7 @@ class Systems(Base):
return response.reason
def set_boot_source_override(self, target, enabled):
"""Shotcut function to set boot source
'''Shotcut function to set boot source
:param target: new boot source. Supported values:
"None",
@ -318,11 +422,12 @@ 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."""
'''Class to manage redfish SystemsCollection data.'''
def __init__(self, url, connection_parameters):
super(SystemsCollection, self).__init__(url, connection_parameters)
@ -331,30 +436,93 @@ class SystemsCollection(BaseCollection):
for link in self.links:
self.systems_list.append(Systems(link, connection_parameters))
class Bios(Base):
"""Class to manage redfish Bios data."""
'''Class to manage redfish Bios data.'''
def __init__(self, url, connection_parameters):
super(Bios, self).__init__(url, connection_parameters)
self.boot = Boot(re.findall(".+/Bios",url)[0]+"/Boot/Settings", connection_parameters)
self.boot = Boot(re.findall('.+/Bios', url)[0] + '/Boot/Settings', connection_parameters)
class Boot(Base):
"""Class to manage redfish Boot data."""
'''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."""
'''Class to manage redfish EthernetInterfacesColkection data.'''
def __init__(self, url, connection_parameters):
super(EthernetInterfacesCollection, self).__init__(url, connection_parameters)
self.ethernet_interfaces_list = []
self.ethernet_interfaces_dict = {}
# 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
# Url returned by the mock up is wrong /redfish/v1/Managers/EthernetInterfaces/1 returns a 404. --> this is not true anymore (2016/01/03)
# The correct one should be /redfish/v1/Managers/1/EthernetInterfaces/1 --> correct by mockup return invalid content (not json)
# Check more than 1 hour for this bug.... grrr....
for link in self.links:
self.ethernet_interfaces_list.append(EthernetInterfaces(link, connection_parameters))
index = re.search(r'EthernetInterfaces/(\w+)', link)
self.ethernet_interfaces_dict[index.group(1)] = EthernetInterfaces(link, connection_parameters)
class EthernetInterfaces(Base):
"""Class to manage redfish EthernetInterfaces data."""
pass
'''Class to manage redfish EthernetInterfaces.'''
def get_mac(self):
'''Get EthernetInterface MacAddress
:returns: string -- interface macaddress or "Not available"
'''
try:
return self.data.MacAddress
except AttributeError:
return "Not available"
def get_fqdn(self):
'''Get EthernetInterface fqdn
:returns: string -- interface fqdn or "Not available"
'''
try:
return self.data.FQDN
except AttributeError:
return "Not available"
def get_ipv4(self):
'''Get EthernetInterface ipv4 address
:returns: list -- interface ip addresses or "Not available"
'''
ipaddresses = []
try:
for ip_settings in self.data.IPv4Addresses:
address = ip_settings['Address']
ipaddresses.append(address)
return ipaddresses
except AttributeError:
return "Not available"
def get_ipv6(self):
'''Get EthernetInterface ipv6 address
:returns: list -- interface ip addresses or "Not available"
'''
ipaddresses = []
try:
for ip_settings in self.data.IPv6Addresses:
address = ip_settings['Address']
ipaddresses.append(address)
return ipaddresses
except AttributeError:
return "Not available"

View File

@ -6,3 +6,4 @@ pbr>=0.6,!=0.7,<1.0
#oslo.log>=1.0,<2.0
Babel>=1.3
tortilla>=0.4.1
Jinja2>=2.7.3