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