From 905a36d660675efda0d3c04cbf8913ba3717b537 Mon Sep 17 00:00:00 2001 From: Uggla Date: Sat, 28 May 2016 12:29:17 +0200 Subject: [PATCH] Add oem data - Add oem structure - Add oem as a redfish submodule. - Modify code and files to avoid circular imports : - types.py contains the Base, BaseCollection and Device class on which all standard and oem classes are inherited. - standard.py will contain the redfish standard classes. - oem/ will contains specific classes for . so oem/hpe will contains hpe specific classes. Note: the goal of python-redfish is not to deal with oem part. So oem will be kept as minimal as possible, however currently some critical hardware characteristics are only available into the oem part that's the reason why we are implementing it. - Add oem class NetworkAdapter - This is mainly to extract mac@ with function get_mac(). - Add classes SmartStorage, ArrayControllersCollection, ArrayControllers, LogicalDrivesCollection, LogicalDrives. - Add Logical drives methods get_capacity(), get_raid. - Improve system template and add a couple of function. - Add get_structured_name(). - Add get_uefi_path(). - However this 2 functions provide invalid content due to the firmware. - Review and inherit from device instead of base for some components. Change-Id: Id13e94d75f85fec7d98f1fa005c37836c244e08a --- redfish-client/templates/system_info.template | 64 +- redfish/main.py | 16 +- redfish/oem/__init__.py | 13 + redfish/oem/hpe.py | 155 ++++ redfish/standard.py | 736 ++++++++++++++++++ redfish/types.py | 710 +---------------- setup.cfg | 1 + 7 files changed, 973 insertions(+), 722 deletions(-) create mode 100644 redfish/oem/__init__.py create mode 100644 redfish/oem/hpe.py create mode 100644 redfish/standard.py diff --git a/redfish-client/templates/system_info.template b/redfish-client/templates/system_info.template index 6e40a63..646b623 100644 --- a/redfish-client/templates/system_info.template +++ b/redfish-client/templates/system_info.template @@ -19,13 +19,13 @@ CPU model : {{ system.get_cpumodel() }} {%- if system.processors_collection %} CPU details : - {%- for cpu_index in system.processors_collection.processors_dict | sort %} - {%- set cpu = system.processors_collection.processors_dict[cpu_index] %} - Processor id {{ cpu_index }} : - Speed : {{ cpu.get_speed() }} - Cores : {{ cpu.get_cores() }} - Threads : {{ cpu.get_threads() }} - {% endfor %} + {%- for cpu_index in system.processors_collection.processors_dict | sort %} + {%- set cpu = system.processors_collection.processors_dict[cpu_index] %} + Processor id {{ cpu_index }} : + Speed : {{ cpu.get_speed() }} + Cores : {{ cpu.get_cores() }} + Threads : {{ cpu.get_threads() }} + {% endfor %} {%- endif %} Available memory : {{ system.get_memory() }} GB Status : State : {{ system.get_status().Health }} / Health : {{ system.get_status().Health }} @@ -47,8 +47,27 @@ Ethernet Interface : Address ipv6 : {{ ei.get_ipv6() | join(', ') }} {%- endfor %} {%- else %} - This system has no ethernet interface + This system has no ethernet interface as Redfish standard data {%- endif %} +Looking for potential OEM information : +{%- if system.data.Oem.Hp %} + Supplemental information from HPE OEM part. + {%- if system.network_adapters_collection %} + {%- for networkadapter_index in system.network_adapters_collection.network_adapters_dict | sort %} + {%- set na = system.network_adapters_collection.network_adapters_dict[networkadapter_index] %} + Network adapter id {{ networkadapter_index }} : + {{ na.get_name() }} + {#- Removing these information because the FW is not providing them correctly + {{ na.get_structured_name() }} + {{ na.get_uefi_path() }} + #} + Mac address : {{ na.get_mac() | join(', ') }} + {%- endfor %} + {%- endif %} +{%- else %} + This system has no supplemental OEM information +{%- endif %} + Simple Storage : {%- if system.simple_storage_collection %} @@ -57,12 +76,33 @@ Simple Storage : Simple Storage id {{ simplestorage_index }} : {{ ss.get_name() }} Status : State : {{ system.get_status().Health }} / Health : {{ system.get_status().Health }} - {%- for dev in ss.get_devices() %} - Device id {{ loop.index }} : {{ dev.Name }} {{ dev.Manufacturer }} {{ dev.Model }} - {%- endfor %} + {%- for dev in ss.get_devices() %} + Device id {{ loop.index }} : {{ dev.Name }} {{ dev.Manufacturer }} {{ dev.Model }} + {%- endfor %} {%- endfor %} {%- else %} - This system has no simple storage + This system has no simple storage as Redfish standard data +{%- endif %} +Looking for potential OEM information : +{%- if system.data.Oem.Hp %} + Supplemental information from HPE OEM part. + {%- if system.smart_storage %} + {%- for array_controllers_index in system.smart_storage.array_controllers_collection.array_controllers_dict | sort %} + {%- set ac = system.smart_storage.array_controllers_collection.array_controllers_dict[array_controllers_index] %} + Array controller id {{ array_controllers_index }} : + {{ ac.get_name() }} + {%- for logical_drives_index in ac.logical_drives_collection.logical_drives_dict | sort %} + {%- set ld = ac.logical_drives_collection.logical_drives_dict[logical_drives_index] %} + Logical drive id {{ logical_drives_index }} : + {{ ld.get_name() }} + Status : State : {{ ld.get_status().Health }} / Health : {{ ld.get_status().Health }} + Capacity : {{ ld.get_capacity() }} MB + Raid : {{ ld.get_raid() }} + {%- endfor %} + {%- endfor %} + {%- endif %} +{%- else %} + This system has no supplemental OEM information {%- endif %} -------------------------------------------------------------------------------- {% endfor %} \ No newline at end of file diff --git a/redfish/main.py b/redfish/main.py index ef6f29d..fdfc1ea 100644 --- a/redfish/main.py +++ b/redfish/main.py @@ -128,7 +128,7 @@ import json from urllib.parse import urlparse, urljoin, urlunparse import requests from . import config -from . import types +from . import standard from . import mapping from . import exception standard_library.install_aliases() @@ -207,8 +207,8 @@ class RedfishConnection(object): config.logger.debug("Root url : %s", self.connection_parameters.rooturl) - self.Root = types.Root(self.connection_parameters.rooturl, - self.connection_parameters) + self.Root = standard.Root(self.connection_parameters.rooturl, + self.connection_parameters) config.logger.info("API Version : %s", self.get_api_version()) mapping.redfish_version = self.get_api_version() @@ -236,21 +236,21 @@ class RedfishConnection(object): # TODO : Add a switch to allow the both structure # =================================================================== - # Types - self.SessionService = types.SessionService( + # standard + self.SessionService = standard.SessionService( self.Root.get_link_url( mapping.redfish_mapper.map_sessionservice()), self.connection_parameters) - self.Managers = types.ManagersCollection( + self.Managers = standard.ManagersCollection( self.Root.get_link_url("Managers"), self.connection_parameters) - self.Systems = types.SystemsCollection( + self.Systems = standard.SystemsCollection( self.Root.get_link_url("Systems"), self.connection_parameters) - self.Chassis = types.ChassisCollection( + self.Chassis = standard.ChassisCollection( self.Root.get_link_url("Chassis"), self.connection_parameters) # self.EventService diff --git a/redfish/oem/__init__.py b/redfish/oem/__init__.py new file mode 100644 index 0000000..19f5e72 --- /dev/null +++ b/redfish/oem/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/redfish/oem/hpe.py b/redfish/oem/hpe.py new file mode 100644 index 0000000..8780404 --- /dev/null +++ b/redfish/oem/hpe.py @@ -0,0 +1,155 @@ +# coding=utf-8 +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from future import standard_library + +import re +from ..types import Base, BaseCollection, Device +standard_library.install_aliases() + +# Global variable + + +class NetworkAdaptersCollection(BaseCollection): + '''Class to manage redfish hpe oem NetworkAdaptersCollection data.''' + def __init__(self, url, connection_parameters): + super(NetworkAdaptersCollection, self).__init__(url, + connection_parameters) + self.network_adapters_dict = {} + + for link in self.links: + index = re.search(r'NetworkAdapters/(\w+)', link) + self.network_adapters_dict[index.group(1)] = NetworkAdapters( + link, connection_parameters) + + +class NetworkAdapters(Device): + '''Class to manage redfish hpe oem NetworkAdapters data.''' + + def get_mac(self): + '''Get NetworkAdapters mac address + + :returns: mac adresses or "Not available" + :rtype: list + + ''' + + macaddresses = [] + + try: + for port in self.data.PhysicalPorts: + mac = port['MacAddress'] + macaddresses.append(mac) + + return macaddresses + except AttributeError: + return "Not available" + + def get_structured_name(self): + '''Get NetworkAdapters StructuredName + + :returns: StructuredName or "Not available" + :rtype: string + + ''' + try: + return self.data.StructuredName + except AttributeError: + return "Not available" + + def get_uefi_path(self): + '''Get networkadapters uefi path + + :returns: UEFIDevicePath or "Not available" + :rtype: string + + ''' + try: + return self.data.UEFIDevicePath + except AttributeError: + return "Not available" + + +class SmartStorage(Base): + '''Class to manage redfish hpe oem SmartStorage data.''' + def __init__(self, url, connection_parameters): + super(SmartStorage, self).__init__(url, connection_parameters) + try: + self.array_controllers_collection = \ + ArrayControllersCollection( + self.get_link_url('ArrayControllers', self.data.Links), + connection_parameters) + + except AttributeError: + # This means we don't have ArrayControllers + self.array_controllers_collection = None + + +class ArrayControllersCollection(BaseCollection): + '''Class to manage redfish hpe oem ArrayControllersCollection data.''' + def __init__(self, url, connection_parameters): + super(ArrayControllersCollection, self).__init__(url, + connection_parameters) + self.array_controllers_dict = {} + + for link in self.links: + index = re.search(r'ArrayControllers/(\w+)', link) + self.array_controllers_dict[index.group(1)] = ArrayControllers( + link, connection_parameters) + + +class ArrayControllers(Device): + '''Class to manage redfish hpe oem ArrayControllers data.''' + def __init__(self, url, connection_parameters): + super(ArrayControllers, self).__init__(url, connection_parameters) + try: + self.logical_drives_collection = \ + LogicalDrivesCollection( + self.get_link_url('LogicalDrives', self.data.Links), + connection_parameters) + + except AttributeError: + # This means we don't have ArrayControllers + self.logical_drives_collection = None + + +class LogicalDrivesCollection(BaseCollection): + '''Class to manage redfish hpe oem LogicalDrivesCollection data.''' + def __init__(self, url, connection_parameters): + super(LogicalDrivesCollection, self).__init__(url, + connection_parameters) + self.logical_drives_dict = {} + + for link in self.links: + index = re.search(r'LogicalDrives/(\w+)', link) + self.logical_drives_dict[index.group(1)] = LogicalDrives( + link, connection_parameters) + + +class LogicalDrives(Device): + '''Class to manage redfish hpe oem LogicalDrives data.''' + def get_capacity(self): + '''Get Logical drive capacity + + :returns: Logical drive capacity or "Not available" + :rtype: string + + ''' + try: + return self.data.CapacityMiB + except AttributeError: + return "Not available" + + def get_raid(self): + '''Get Logical drive raid configuration + + :returns: Logical drive raid configuration or "Not available" + :rtype: string + + ''' + try: + return self.data.Raid + except AttributeError: + return "Not available" diff --git a/redfish/standard.py b/redfish/standard.py new file mode 100644 index 0000000..ddb96e1 --- /dev/null +++ b/redfish/standard.py @@ -0,0 +1,736 @@ +# coding=utf-8 +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from future import standard_library + +import re +from urllib.parse import urljoin +import requests +from .types import Base, BaseCollection, Device +from . import mapping +from . import exception +from .oem import hpe +standard_library.install_aliases() + + +class Root(Base): + '''Class to manage redfish Root data.''' + def get_api_version(self): + '''Return api version. + + :returns: api version + :rtype: string + :raises: AttributeError + + ''' + try: + version = self.data.RedfishVersion + except AttributeError: + version = self.data.ServiceVersion + + version = version.replace('.', '') + version = version[0] + '.' + version[1:] + return(version) + + def get_api_UUID(self): + '''Return api UUID. + + :returns: api UUID + :rtype: string + + ''' + return self.data.UUID + + +class SessionService(Base): + '''Class to manage redfish SessionService data.''' + pass + + +class Managers(Device): + '''Class to manage redfish Managers.''' + def __init__(self, url, connection_parameters): + super(Managers, self).__init__(url, connection_parameters) + try: + # 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('EthernetInterfaces'), + connection_parameters) + + # 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_type(self): + '''Get manager type + + :returns: manager type or "Not available" + :rtype: string + + ''' + try: + return self.data.ManagerType + except AttributeError: + return "Not available" + + def get_firmware_version(self): + '''Get firmware version of the manager + + :returns: string -- bios version or "Not available" + + ''' + try: + 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_managed_chassis(self): + '''Get managed chassis ids by the manager + + :returns: chassis ids or "Not available" + :rtype: list + + ''' + 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: systems ids or "Not available" + :rtype: list + + ''' + 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" + + def reset(self): + '''Reset the manager. + + :returns: string -- http response of POST request + + ''' + # Craft the request + link = getattr(self.data.Actions, "#Manager.Reset") + link = link.target + + reset_url = urljoin(self.url, link) + + response = requests.post( + reset_url, + verify=self.connection_parameters.verify_cert, + headers=self.connection_parameters.headers) + # TODO : treat response. + return response + + +class ManagersCollection(BaseCollection): + '''Class to manage redfish ManagersCollection data.''' + def __init__(self, url, connection_parameters): + '''Class constructor''' + super(ManagersCollection, self).__init__(url, connection_parameters) + self.managers_dict = {} + for link in self.links: + index = re.search(r'Managers/(\w+)', link) + self.managers_dict[index.group(1)] = Managers( + link, connection_parameters) + + +class Systems(Device): + '''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''' + super(Systems, self).__init__(url, connection_parameters) + try: + self.bios = Bios(url + 'Bios/Settings', connection_parameters) + except: + pass + + try: + self.ethernet_interfaces_collection = \ + EthernetInterfacesCollection( + self.get_link_url('EthernetInterfaces'), + connection_parameters) + except AttributeError: + # This means we don't have EthernetInterfaces + self.ethernet_interfaces_collection = None + + try: + self.processors_collection = \ + ProcessorsCollection( + self.get_link_url('Processors'), + connection_parameters) + except AttributeError: + # This means we don't have Processors detailed data + self.processors_collection = None + + try: + self.simple_storage_collection = \ + SimpleStorageCollection( + self.get_link_url('SimpleStorage'), + connection_parameters) + except AttributeError: + # This means we don't have Processors detailed data + self.simple_storage_collection = None + + try: + self.data.Oem.Hp + try: + self.network_adapters_collection = \ + hpe.NetworkAdaptersCollection( + self.get_link_url('NetworkAdapters', + self.data.Oem.Hp.Links), + connection_parameters) + except AttributeError: + # This means we don't have NetworkAdapters + self.network_adapters_collection = None + try: + self.smart_storage = \ + hpe.SmartStorage( + self.get_link_url('SmartStorage', + self.data.Oem.Hp.Links), + connection_parameters) + except AttributeError: + # This means we don't have SmartStorage + self.smart_storage = None + except AttributeError: + # This means we don't have oem data + pass + + def reset_system(self): + '''Force reset of the system. + + :returns: string -- http response of POST request + + ''' + # Craft the request + action = dict() + action['Action'] = 'Reset' + action['ResetType'] = 'ForceRestart' + + # Debug the url and perform the POST action + # print self.api_url + response = self.api_url.post( + verify=self.connection_parameters.verify_cert, + headers=self.connection_parameters.headers, + data=action) + # TODO : treat response. + return response + + def get_bios_version(self): + '''Get bios version of the system. + + :returns: bios version or "Not available" + :rtype: string + + ''' + try: + return self.data.BiosVersion + except AttributeError: + return "Not available" + + def get_hostname(self): + '''Get hostname of the system. + + :returns: hostname or "Not available" + :rtype: string + + ''' + try: + return self.data.HostName + except AttributeError: + return "Not available" + + def get_indicatorled(self): + '''Get indicatorled of the system. + + :returns: indicatorled status or "Not available" + :rtype: string + + ''' + try: + return self.data.IndicatorLED + except AttributeError: + return "Not available" + + def get_power(self): + '''Get power status of the system. + + :returns: system power state or "Not available" + :rtype: string + + ''' + try: + return self.data.PowerState + except AttributeError: + return "Not available" + + def get_description(self): + '''Get description of the system. + + :returns: system description or "Not available" + :rtype: string + + ''' + try: + return self.data.Description + except AttributeError: + return "Not available" + + def get_cpucount(self): + '''Get the number of cpu in the system. + + :returns: number of cpu or "Not available" + :rtype: string + + ''' + try: + return self.data.ProcessorSummary.Count + except AttributeError: + return "Not available" + + def get_cpumodel(self): + '''Get the cpu model available in the system. + + :returns: cpu model or "Not available" + :rtype: string + + ''' + try: + return self.data.ProcessorSummary.Model + except AttributeError: + return "Not available" + + def get_memory(self): + '''Get the memory available in the system. + + :returns: memory available or "Not available" + :rtype: string + + ''' + try: + return self.data.MemorySummary.TotalSystemMemoryGiB + except AttributeError: + return "Not available" + + def get_type(self): + '''Get system type + + :returns: system type or "Not available" + :rtype: string + + ''' + try: + return self.data.SystemType + except AttributeError: + return "Not available" + + def get_chassis(self): + '''Get chassis ids used by the system + + :returns: chassis ids or "Not available" + :rtype: list + + ''' + chassis_list = [] + links = getattr(self.data, mapping.redfish_mapper.map_links(self.data)) + + try: + for chassis in links.Chassis: + 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_managers(self): + '''Get manager ids used by the system + + :returns: managers ids or "Not available" + :rtype: list + + ''' + managers_list = [] + links = getattr(self.data, mapping.redfish_mapper.map_links(self.data)) + + try: + for manager in links.ManagedBy: + result = re.search( + r'Managers/(\w+)', + manager[mapping.redfish_mapper.map_links_ref(manager)]) + managers_list.append(result.group(1)) + return managers_list + except AttributeError: + return "Not available" + + def set_parameter_json(self, value): + '''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(), + verify=self.connection_parameters.verify_cert, + headers=self.connection_parameters.headers, + data=value) + return response.reason + + def set_boot_source_override(self, target, enabled): + '''Shotcut function to set boot source + + :param target: new boot source. Supported values: + "None", + "Pxe", + "Floppy", + "Cd", + "Usb", + "Hdd", + "BiosSetup", + "Utilities", + "Diags", + "UefiShell", + "UefiTarget" + :param enabled: Supported values: + "Disabled", + "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 SystemsCollection data.''' + def __init__(self, url, connection_parameters): + super(SystemsCollection, self).__init__(url, connection_parameters) + + self.systems_dict = {} + + for link in self.links: + index = re.search(r'Systems/(\w+)', link) + self.systems_dict[index.group(1)] = Systems( + link, connection_parameters) + + +class Bios(Base): + '''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) + + +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_dict = {} + + # 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: + 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.''' + def get_mac(self): + '''Get EthernetInterface MacAddress + + :returns: string -- interface macaddress or "Not available" + + ''' + try: + # Proliant firmware seems to not follow redfish systax + return self.data.MacAddress + except AttributeError: + 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" + + +class ProcessorsCollection(BaseCollection): + '''Class to manage redfish ProcessorsCollection data.''' + def __init__(self, url, connection_parameters): + super(ProcessorsCollection, + self).__init__(url, connection_parameters) + + self.processors_dict = {} + + for link in self.links: + index = re.search(r'Processors/(\w+)', link) + self.processors_dict[index.group(1)] = \ + Processors(link, connection_parameters) + + +class Processors(Base): + '''Class to manage redfish Processors.''' + def get_speed(self): + '''Get processor speed + + :returns: processor speed or "Not available" + :rtype: string + + ''' + try: + return self.data.MaxSpeedMHz + except AttributeError: + return "Not available" + + def get_cores(self): + '''Get processor cores number + + :returns: cores number or "Not available" + :rtype: string + + ''' + try: + return self.data.TotalCores + except AttributeError: + return "Not available" + + def get_threads(self): + '''Get processor threads number + + :returns: threads number or "Not available" + :rtype: string + + ''' + try: + return self.data.TotalThreads + except AttributeError: + return "Not available" + + +class SimpleStorageCollection(BaseCollection): + '''Class to manage redfish SimpleStorageCollection data.''' + def __init__(self, url, connection_parameters): + super(SimpleStorageCollection, + self).__init__(url, connection_parameters) + + self.simple_storage_dict = {} + + for link in self.links: + index = re.search(r'SimpleStorage/(\w+)', link) + self.simple_storage_dict[index.group(1)] = \ + SimpleStorage(link, connection_parameters) + + +class SimpleStorage(Base): + '''Class to manage redfish SimpleStorage''' + def get_status(self): + '''Get storage status + + :returns: storage status or "Not available" + :rtype: dict + + ''' + try: + return self.data.Status + except AttributeError: + return "Not available" + + def get_devices(self): + '''Get storage devices + + :returns: storage devices or "Not available" + :rtype: list of dict + + ''' + try: + return self.data.Devices + except AttributeError: + return "Not available" + + +class ChassisCollection(BaseCollection): + '''Class to manage redfish ChassisCollection data.''' + def __init__(self, url, connection_parameters): + super(ChassisCollection, self).__init__(url, connection_parameters) + + self.chassis_dict = {} + + for link in self.links: + index = re.search(r'Chassis/(\w+)', link) + self.chassis_dict[index.group(1)] = Chassis( + link, connection_parameters) + + +class Chassis(Device): + '''Class to manage redfish Chassis data.''' + def __init__(self, url, connection_parameters): + '''Class constructor''' + super(Chassis, self).__init__(url, connection_parameters) + + try: + self.thermal = Thermal(self.get_link_url('Thermal'), + connection_parameters) + except AttributeError: + self.thermal = None + + try: + self.power = Power(self.get_link_url('Power'), + connection_parameters) + except AttributeError: + self.Power = None + + def get_type(self): + '''Get chassis type + + :returns: chassis type or "Not available" + :rtype: string + + ''' + try: + return self.data.ChassisType + except AttributeError: + return "Not available" + + +class Thermal(Base): + '''Class to manage redfish Thermal data.''' + def get_temperatures(self): + '''Get chassis sensors name and temparature + + :returns: chassis sensor and temperature + :rtype: dict + + ''' + temperatures = {} + + try: + for sensor in self.data.Temperatures: + temperatures[sensor.Name] = sensor.ReadingCelsius + return temperatures + except AttributeError: + return "Not available" + + def get_fans(self): + '''Get chassis fan name and rpm + + :returns: chassis fan and rpm + :rtype: dict + + ''' + fans = {} + + try: + for fan in self.data.Fans: + fans[fan.FanName] = fan.ReadingRPM + return fans + except AttributeError: + return "Not available" + + +class Power(Base): + '''Class to manage redfish Power data.''' + pass diff --git a/redfish/types.py b/redfish/types.py index 27e3b8d..fd7c39a 100644 --- a/redfish/types.py +++ b/redfish/types.py @@ -7,7 +7,6 @@ from future import standard_library from builtins import object import pprint -import re from urllib.parse import urljoin import requests import simplejson @@ -18,7 +17,6 @@ from . import mapping from . import exception standard_library.install_aliases() -standard_library.install_aliases() # Global variable @@ -57,24 +55,29 @@ class Base(object): raise exception.InvalidRedfishContentException(msg) config.logger.debug(pprint.PrettyPrinter(indent=4).pformat(self.data)) - def get_link_url(self, link_type): + def get_link_url(self, link_type, data_subset=None): '''Need to be explained. :param parameter_name: name of the parameter :returns: string -- parameter value ''' + if not data_subset: + data = self.data + else: + data = data_subset + self.links = [] # Manage standard < 1.0 if float(mapping.redfish_version) < 1.00: - links = getattr(self.data, mapping.redfish_mapper.map_links()) + links = getattr(data, mapping.redfish_mapper.map_links()) if link_type in links: return urljoin( self.url, links[link_type][mapping.redfish_mapper.map_links_ref()]) raise AttributeError else: - links = getattr(self.data, link_type) + links = getattr(data, link_type) link = getattr(links, mapping.redfish_mapper.map_links_ref()) return urljoin(self.url, link) @@ -268,700 +271,3 @@ class Device(Base): return self.data.PartNumber except AttributeError: return "Not available" - - -class Root(Base): - '''Class to manage redfish Root data.''' - def get_api_version(self): - '''Return api version. - - :returns: api version - :rtype: string - :raises: AttributeError - - ''' - try: - version = self.data.RedfishVersion - except AttributeError: - version = self.data.ServiceVersion - - version = version.replace('.', '') - version = version[0] + '.' + version[1:] - return(version) - - def get_api_UUID(self): - '''Return api UUID. - - :returns: api UUID - :rtype: string - - ''' - return self.data.UUID - - -class SessionService(Base): - '''Class to manage redfish SessionService data.''' - pass - - -class Managers(Device): - '''Class to manage redfish Managers.''' - def __init__(self, url, connection_parameters): - super(Managers, self).__init__(url, connection_parameters) - try: - # 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('EthernetInterfaces'), - connection_parameters) - - # 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_type(self): - '''Get manager type - - :returns: manager type or "Not available" - :rtype: string - - ''' - try: - return self.data.ManagerType - except AttributeError: - return "Not available" - - def get_firmware_version(self): - '''Get firmware version of the manager - - :returns: string -- bios version or "Not available" - - ''' - try: - 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_managed_chassis(self): - '''Get managed chassis ids by the manager - - :returns: chassis ids or "Not available" - :rtype: list - - ''' - 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: systems ids or "Not available" - :rtype: list - - ''' - 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" - - def reset(self): - '''Reset the manager. - - :returns: string -- http response of POST request - - ''' - # Craft the request - link = getattr(self.data.Actions, "#Manager.Reset") - link = link.target - - reset_url = urljoin(self.url, link) - - response = requests.post( - reset_url, - verify=self.connection_parameters.verify_cert, - headers=self.connection_parameters.headers) - # TODO : treat response. - return response - - -class ManagersCollection(BaseCollection): - '''Class to manage redfish ManagersCollection data.''' - def __init__(self, url, connection_parameters): - '''Class constructor''' - super(ManagersCollection, self).__init__(url, connection_parameters) - self.managers_dict = {} - for link in self.links: - index = re.search(r'Managers/(\w+)', link) - self.managers_dict[index.group(1)] = Managers( - link, connection_parameters) - - -class Systems(Device): - '''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''' - super(Systems, self).__init__(url, connection_parameters) - try: - self.bios = Bios(url + 'Bios/Settings', connection_parameters) - except: - pass - - try: - self.ethernet_interfaces_collection = \ - EthernetInterfacesCollection( - self.get_link_url('EthernetInterfaces'), - connection_parameters) - except AttributeError: - # This means we don't have EthernetInterfaces - self.ethernet_interfaces_collection = None - - try: - self.processors_collection = \ - ProcessorsCollection( - self.get_link_url('Processors'), - connection_parameters) - except AttributeError: - # This means we don't have Processors detailed data - self.processors_collection = None - - try: - self.simple_storage_collection = \ - SimpleStorageCollection( - self.get_link_url('SimpleStorage'), - connection_parameters) - except AttributeError: - # This means we don't have Processors detailed data - self.simple_storage_collection = None - - def reset_system(self): - '''Force reset of the system. - - :returns: string -- http response of POST request - - ''' - # Craft the request - action = dict() - action['Action'] = 'Reset' - action['ResetType'] = 'ForceRestart' - - # Debug the url and perform the POST action - # print self.api_url - response = self.api_url.post( - verify=self.connection_parameters.verify_cert, - headers=self.connection_parameters.headers, - data=action) - # TODO : treat response. - return response - - def get_bios_version(self): - '''Get bios version of the system. - - :returns: bios version or "Not available" - :rtype: string - - ''' - try: - return self.data.BiosVersion - except AttributeError: - return "Not available" - - def get_hostname(self): - '''Get hostname of the system. - - :returns: hostname or "Not available" - :rtype: string - - ''' - try: - return self.data.HostName - except AttributeError: - return "Not available" - - def get_indicatorled(self): - '''Get indicatorled of the system. - - :returns: indicatorled status or "Not available" - :rtype: string - - ''' - try: - return self.data.IndicatorLED - except AttributeError: - return "Not available" - - def get_power(self): - '''Get power status of the system. - - :returns: system power state or "Not available" - :rtype: string - - ''' - try: - return self.data.PowerState - except AttributeError: - return "Not available" - - def get_description(self): - '''Get description of the system. - - :returns: system description or "Not available" - :rtype: string - - ''' - try: - return self.data.Description - except AttributeError: - return "Not available" - - def get_cpucount(self): - '''Get the number of cpu in the system. - - :returns: number of cpu or "Not available" - :rtype: string - - ''' - try: - return self.data.ProcessorSummary.Count - except AttributeError: - return "Not available" - - def get_cpumodel(self): - '''Get the cpu model available in the system. - - :returns: cpu model or "Not available" - :rtype: string - - ''' - try: - return self.data.ProcessorSummary.Model - except AttributeError: - return "Not available" - - def get_memory(self): - '''Get the memory available in the system. - - :returns: memory available or "Not available" - :rtype: string - - ''' - try: - return self.data.MemorySummary.TotalSystemMemoryGiB - except AttributeError: - return "Not available" - - def get_type(self): - '''Get system type - - :returns: system type or "Not available" - :rtype: string - - ''' - try: - return self.data.SystemType - except AttributeError: - return "Not available" - - def get_chassis(self): - '''Get chassis ids used by the system - - :returns: chassis ids or "Not available" - :rtype: list - - ''' - chassis_list = [] - links = getattr(self.data, mapping.redfish_mapper.map_links(self.data)) - - try: - for chassis in links.Chassis: - 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_managers(self): - '''Get manager ids used by the system - - :returns: managers ids or "Not available" - :rtype: list - - ''' - managers_list = [] - links = getattr(self.data, mapping.redfish_mapper.map_links(self.data)) - - try: - for manager in links.ManagedBy: - result = re.search( - r'Managers/(\w+)', - manager[mapping.redfish_mapper.map_links_ref(manager)]) - managers_list.append(result.group(1)) - return managers_list - except AttributeError: - return "Not available" - - def set_parameter_json(self, value): - '''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(), - verify=self.connection_parameters.verify_cert, - headers=self.connection_parameters.headers, - data=value) - return response.reason - - def set_boot_source_override(self, target, enabled): - '''Shotcut function to set boot source - - :param target: new boot source. Supported values: - "None", - "Pxe", - "Floppy", - "Cd", - "Usb", - "Hdd", - "BiosSetup", - "Utilities", - "Diags", - "UefiShell", - "UefiTarget" - :param enabled: Supported values: - "Disabled", - "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 SystemsCollection data.''' - def __init__(self, url, connection_parameters): - super(SystemsCollection, self).__init__(url, connection_parameters) - - self.systems_dict = {} - - for link in self.links: - index = re.search(r'Systems/(\w+)', link) - self.systems_dict[index.group(1)] = Systems( - link, connection_parameters) - - -class Bios(Base): - '''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) - - -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_dict = {} - - # 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: - 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.''' - def get_mac(self): - '''Get EthernetInterface MacAddress - - :returns: string -- interface macaddress or "Not available" - - ''' - try: - # Proliant firmware seems to not follow redfish systax - return self.data.MacAddress - except AttributeError: - 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" - - -class ProcessorsCollection(BaseCollection): - '''Class to manage redfish ProcessorsCollection data.''' - def __init__(self, url, connection_parameters): - super(ProcessorsCollection, - self).__init__(url, connection_parameters) - - self.processors_dict = {} - - for link in self.links: - index = re.search(r'Processors/(\w+)', link) - self.processors_dict[index.group(1)] = \ - Processors(link, connection_parameters) - - -class Processors(Base): - '''Class to manage redfish Processors.''' - def get_speed(self): - '''Get processor speed - - :returns: processor speed or "Not available" - :rtype: string - - ''' - try: - return self.data.MaxSpeedMHz - except AttributeError: - return "Not available" - - def get_cores(self): - '''Get processor cores number - - :returns: cores number or "Not available" - :rtype: string - - ''' - try: - return self.data.TotalCores - except AttributeError: - return "Not available" - - def get_threads(self): - '''Get processor threads number - - :returns: threads number or "Not available" - :rtype: string - - ''' - try: - return self.data.TotalThreads - except AttributeError: - return "Not available" - - -class SimpleStorageCollection(BaseCollection): - '''Class to manage redfish SimpleStorageCollection data.''' - def __init__(self, url, connection_parameters): - super(SimpleStorageCollection, - self).__init__(url, connection_parameters) - - self.simple_storage_dict = {} - - for link in self.links: - index = re.search(r'SimpleStorage/(\w+)', link) - self.simple_storage_dict[index.group(1)] = \ - SimpleStorage(link, connection_parameters) - - -class SimpleStorage(Base): - '''Class to manage redfish SimpleStorage''' - def get_status(self): - '''Get storage status - - :returns: storage status or "Not available" - :rtype: dict - - ''' - try: - return self.data.Status - except AttributeError: - return "Not available" - - def get_devices(self): - '''Get storage devices - - :returns: storage devices or "Not available" - :rtype: list of dict - - ''' - try: - return self.data.Devices - except AttributeError: - return "Not available" - - -class ChassisCollection(BaseCollection): - '''Class to manage redfish ChassisCollection data.''' - def __init__(self, url, connection_parameters): - super(ChassisCollection, self).__init__(url, connection_parameters) - - self.chassis_dict = {} - - for link in self.links: - index = re.search(r'Chassis/(\w+)', link) - self.chassis_dict[index.group(1)] = Chassis( - link, connection_parameters) - - -class Chassis(Device): - '''Class to manage redfish Chassis data.''' - def __init__(self, url, connection_parameters): - '''Class constructor''' - super(Chassis, self).__init__(url, connection_parameters) - - try: - self.thermal = Thermal(self.get_link_url('Thermal'), - connection_parameters) - except AttributeError: - self.thermal = None - - try: - self.power = Power(self.get_link_url('Power'), - connection_parameters) - except AttributeError: - self.Power = None - - def get_type(self): - '''Get chassis type - - :returns: chassis type or "Not available" - :rtype: string - - ''' - try: - return self.data.ChassisType - except AttributeError: - return "Not available" - - -class Thermal(Base): - '''Class to manage redfish Thermal data.''' - def get_temperatures(self): - '''Get chassis sensors name and temparature - - :returns: chassis sensor and temperature - :rtype: dict - - ''' - temperatures = {} - - try: - for sensor in self.data.Temperatures: - temperatures[sensor.Name] = sensor.ReadingCelsius - return temperatures - except AttributeError: - return "Not available" - - def get_fans(self): - '''Get chassis fan name and rpm - - :returns: chassis fan and rpm - :rtype: dict - - ''' - fans = {} - - try: - for fan in self.data.Fans: - fans[fan.FanName] = fan.ReadingRPM - return fans - except AttributeError: - return "Not available" - - -class Power(Base): - '''Class to manage redfish Power data.''' - pass diff --git a/setup.cfg b/setup.cfg index 627d633..6849e3d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,7 @@ classifier = [files] packages = redfish + redfish/oem scripts = redfish-client/redfish-client