From b84dd6d2d93bb775591967d3f5fb92fd242b7186 Mon Sep 17 00:00:00 2001
From: Bruno Cornec <bruno@victoria.frmug.org>
Date: Sat, 30 Mar 2019 02:25:59 +0100
Subject: [PATCH] Add a getserial command to redfish-client

- print Part or Serial numbers of Chassis and Systems
  using a specific jinja2 template
- for HPE servers, print them for NICs and SmartArrays, and Physical
  drives behind (not seen with calls on standard components)

Change-Id: I741c97847a07e126d7f34ceaa4695f9a8cdcec2b
---
 redfish-client/redfish-client                 | 31 ++++---
 redfish-client/redfish-client_usage.txt       |  1 +
 redfish-client/templates/serial_info.template | 81 +++++++++++++++++++
 redfish/oem/hpe.py                            | 43 ++++++++++
 redfish/standard.py                           |  6 +-
 redfish/types.py                              | 37 +++++++++
 6 files changed, 185 insertions(+), 14 deletions(-)
 create mode 100644 redfish-client/templates/serial_info.template

diff --git a/redfish-client/redfish-client b/redfish-client/redfish-client
index 8bc3bfc..fb09bf6 100755
--- a/redfish-client/redfish-client
+++ b/redfish-client/redfish-client
@@ -247,6 +247,11 @@ if __name__ == '__main__':
         # Display system information using jinja2 template
         render_template("system_info.template")
 
+    def display_part_serial_numbers(redfish_data):
+        # Display PartNumbers and SerialNumber entries
+        # information using jinja2 template
+        render_template("serial_info.template")
+
     def render_template(template):
         try:
             template = jinja2_env.get_template(template)
@@ -396,8 +401,8 @@ if __name__ == '__main__':
                                          arguments['<changed_value>'])
             logger.debug(inventory.data)
             inventory.save()
-    elif arguments['getinfo'] is True:
-        logger.debug('getinfo command')
+    elif arguments['getinfo'] is True or arguments['getserial'] is True:
+        logger.debug('get commands')
         # If manager is not defined set it to 'default'
         if not arguments['<manager_name>']:
             manager_name = 'default'
@@ -416,14 +421,18 @@ if __name__ == '__main__':
             redfish_data = get_redfish_data(connection_parameters, False)
         else:
             redfish_data = get_redfish_data(connection_parameters, True)
-        if arguments['manager'] is True:
-            logger.debug("Manager commands")
-            display_manager_info(redfish_data)
-        elif arguments['system'] is True:
-            logger.debug("system commands")
-            display_system_info(redfish_data)
-        elif arguments['chassis'] is True:
-            logger.debug("chassis commands")
-            display_chassis_info(redfish_data)
+        if arguments['getinfo'] is True:
+            if arguments['manager'] is True:
+                logger.debug("Manager commands")
+                display_manager_info(redfish_data)
+            elif arguments['system'] is True:
+                logger.debug("system commands")
+                display_system_info(redfish_data)
+            elif arguments['chassis'] is True:
+                logger.debug("chassis commands")
+                display_chassis_info(redfish_data)
+        if arguments['getserial'] is True:
+            logger.debug("serial & part number commands")
+            display_part_serial_numbers(redfish_data)
     logger.info("Client session terminated")
     sys.exit(0)
diff --git a/redfish-client/redfish-client_usage.txt b/redfish-client/redfish-client_usage.txt
index 762d6d8..f709aae 100644
--- a/redfish-client/redfish-client_usage.txt
+++ b/redfish-client/redfish-client_usage.txt
@@ -9,6 +9,7 @@ Usage:
   redfish-client [options] manager getinfo [<manager_name>]
   redfish-client [options] chassis getinfo [<manager_name>]
   redfish-client [options] system getinfo [<manager_name>]
+  redfish-client [options] getserial [<manager_name>]
   redfish-client (-h | --help)
   redfish-client --version
 
diff --git a/redfish-client/templates/serial_info.template b/redfish-client/templates/serial_info.template
new file mode 100644
index 0000000..154c7cc
--- /dev/null
+++ b/redfish-client/templates/serial_info.template
@@ -0,0 +1,81 @@
+Redfish API version:  {{ r.get_api_version() }}
+{{ r.Root.get_name() }}
+
+Part|Serial Numbers information:
+================================
+{% for chassis_index in r.Chassis.chassis_dict | sort %}
+{%- set chassis = r.Chassis.chassis_dict[chassis_index] %}
+Chassis #{{ chassis_index }}:
+Name: {{ chassis.get_name() }}
+Manufacturer: {{ chassis.get_manufacturer() }}
+Model: {{ chassis.get_model() }}
+Chassis Type: {{ chassis.get_type() }}
+Part#: {{ chassis.get_part_number() }}
+SKU: {{ chassis.get_sku() }}
+Serial#: {{ chassis.get_serial_number() }}
+AssetTag: {{ chassis.get_asset_tag() }}
+FW version: {{ chassis.get_fw_version() }}
+{%- endfor %}
+
+--------------------------------------------------------------------------------
+{% for system_index in r.Systems.systems_dict | sort %}
+{%- set system = r.Systems.systems_dict[system_index] %}
+System #{{ system_index }}:
+Name: {{ system.get_name() }}
+Type: {{ system.get_type() }}
+Model: {{ system.get_model() }}
+SKU: {{ system.get_sku() }}
+Serial: {{ system.get_serial_number() }}
+FW version: {{ system.get_fw_version() }}
+CPU number: {{ system.get_cpucount() }}
+CPU model: {{ system.get_cpumodel() }}
+Available memory: {{ system.get_memory() }} GB
+Status: State: {{ system.get_status().Health }} / Health: {{ system.get_status().Health }}
+Power: {{ system.get_power() }}
+{%- if system.data.Oem.Hpe or system.data.Oem.Hp %}
+{%- if system.network_adapters_collection %}
+{%- for nic_index in system.network_adapters_collection.network_adapters_dict | sort %}
+	{%- if system.network_adapters_collection.network_adapters_dict[nic_index] %}
+	{%- set na = system.network_adapters_collection.network_adapters_dict[nic_index] %}
+Network Adapter #{{ nic_index }}:
+	Name: {{ na.get_name() }}
+	Mac address:
+	{%- for mac_index in  na.get_mac() | sort %}
+		Mac #{{ loop.index }}: {{ mac_index }}
+	{%- endfor %}
+	Serial#: {{ na.get_serial_number() }}
+	Part#: {{ na.get_part_number() }}
+	FW version: {{ na.get_fw_version() }}
+	{%- endif %}
+{%- endfor %}
+{%- endif %}
+{%- endif %}
+
+{%- if system.data.Oem.Hpe or system.data.Oem.Hp %}
+{%- if system.smart_storage %}
+{%- for ac_index in system.smart_storage.array_controllers_collection.array_controllers_dict | sort %}
+	{%- set ac = system.smart_storage.array_controllers_collection.array_controllers_dict[ac_index] %}
+Array controller #{{ ac_index }}:
+	Model: {{ ac.get_model() }}
+	Serial #: {{ ac.get_serial_number() }}
+	Part #: {{ ac.get_part_number() }}
+	{%- 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 #{{ logical_drives_index }}:
+		Capacity: {{ ld.get_capacity() }} MB
+		Raid Level: {{ ld.get_raid() }}
+	{%- endfor %}
+	{%- for physical_drives_index in ac.physical_drives_collection.physical_drives_dict | sort %}
+		{%- set pd = ac.physical_drives_collection.physical_drives_dict[physical_drives_index] %}
+	Physical drive #{{ physical_drives_index }}:
+		{{ pd.get_model() }}
+		Capacity: {{ pd.get_capacity() }} MB
+		Serial #: {{ pd.get_serial_number() }}
+		Part #: {{ pd.get_part_number() }}
+		FW: {{ pd.get_fw_version() }}
+	{%- endfor %}
+{%- endfor %}
+{%- endif %}
+{%- endif %}
+--------------------------------------------------------------------------------
+{% endfor %}
diff --git a/redfish/oem/hpe.py b/redfish/oem/hpe.py
index 8780404..3b042c3 100644
--- a/redfish/oem/hpe.py
+++ b/redfish/oem/hpe.py
@@ -114,6 +114,16 @@ class ArrayControllers(Device):
             # This means we don't have ArrayControllers
             self.logical_drives_collection = None
 
+        try:
+            self.physical_drives_collection = \
+                PhysicalDrivesCollection(
+                    self.get_link_url('PhysicalDrives', self.data.Links),
+                    connection_parameters)
+
+        except AttributeError:
+            # This means we don't have ArrayControllers
+            self.physical_drives_collection = None
+
 
 class LogicalDrivesCollection(BaseCollection):
     '''Class to manage redfish hpe oem LogicalDrivesCollection data.'''
@@ -153,3 +163,36 @@ class LogicalDrives(Device):
             return self.data.Raid
         except AttributeError:
             return "Not available"
+
+
+class PhysicalDrivesCollection(BaseCollection):
+    '''Class to manage redfish hpe oem PhysicalDrivesCollection data.'''
+    def __init__(self, url, connection_parameters):
+        super(PhysicalDrivesCollection, self).__init__(url,
+                                                       connection_parameters)
+        self.physical_drives_dict = {}
+
+        for link in self.links:
+            index = re.search(r'DiskDrives/(\w+)', link)
+            self.physical_drives_dict[index.group(1)] = DiskDrives(
+                link, connection_parameters)
+
+
+class DiskDrives(Device):
+    '''Class to manage redfish hpe oem DiskDrives 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"
+
+
+class StorageEnclosures(Device):
+    '''Class to manage redfish hpe oem StorageEnclosures data.'''
+    pass
diff --git a/redfish/standard.py b/redfish/standard.py
index c28ab2b..8fa216c 100644
--- a/redfish/standard.py
+++ b/redfish/standard.py
@@ -523,7 +523,7 @@ class EthernetInterfacesCollection(BaseCollection):
                 EthernetInterfaces(link, connection_parameters)
 
 
-class EthernetInterfaces(Base):
+class EthernetInterfaces(Device):
     '''Class to manage redfish EthernetInterfaces.'''
     def get_mac(self):
         '''Get EthernetInterface MacAddress
@@ -602,7 +602,7 @@ class ProcessorsCollection(BaseCollection):
                 Processors(link, connection_parameters)
 
 
-class Processors(Base):
+class Processors(Device):
     '''Class to manage redfish Processors.'''
     def get_speed(self):
         '''Get processor speed
@@ -655,7 +655,7 @@ class SimpleStorageCollection(BaseCollection):
                 SimpleStorage(link, connection_parameters)
 
 
-class SimpleStorage(Base):
+class SimpleStorage(Device):
     '''Class to manage redfish SimpleStorage'''
     def get_status(self):
         '''Get storage status
diff --git a/redfish/types.py b/redfish/types.py
index fd7c39a..3e9bfc6 100644
--- a/redfish/types.py
+++ b/redfish/types.py
@@ -271,3 +271,40 @@ class Device(Base):
             return self.data.PartNumber
         except AttributeError:
             return "Not available"
+
+    def get_name(self):
+        '''Get name of the device.
+
+        :returns: name or "Not available"
+        :rtype: string
+
+        '''
+        try:
+            return self.data.Name
+        except AttributeError:
+            try:
+                return self.data.ProductName
+            except AttributeError:
+                return "Not available"
+
+    def get_fw_version(self):
+        '''Get firmware version of the device.
+
+        :returns: firmware version or "Not available"
+        :rtype: string
+
+        '''
+        try:
+            return self.data.FirmwareVersion.Current.VersionString
+        except AttributeError:
+            try:
+                return self.data.Firmware.Current.VersionString
+            except AttributeError:
+                # For some NICs
+                try:
+                    return self.data.FirmwareVersion
+                except AttributeError:
+                    try:
+                        return self.data.Firmware
+                    except AttributeError:
+                        return "Not available"