Skip to content

Commit

Permalink
Merge pull request #12 from DMTF/Inventory-Tool
Browse files Browse the repository at this point in the history
Inventory tool
  • Loading branch information
mraineri authored Jun 27, 2019
2 parents 6971d17 + 9676b21 commit 4cc08d1
Show file tree
Hide file tree
Showing 9 changed files with 355 additions and 4 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ It then traverses the Chassis Collection for the Service, and reads their respec
Using the information from those resources, it will build a sensor table and print the information collected.


### System Inventory

```
usage: rf_sys_inventory [-h] --user USER --password PASSWORD --rhost RHOST
[--details] [--noabsent]
A tool to walk a Redfish service and list component information
required arguments:
--user USER, -u USER The user name for authentication
--password PASSWORD, -p PASSWORD
The password for authentication
--rhost RHOST, -r RHOST
The address of the Redfish service (with scheme)
optional arguments:
-h, --help show this help message and exit
--details, -details Indicates if the full details of each component should
be shown
--noabsent, -noabsent
Indicates if absent devices should be skipped
```

Example: `rf_sys_inventory -u root -p root -r https://192.168.1.100 -details`

The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments.
It then traverses the Chassis Collection for the Service, and collects component information for Processors, Memory, Drives, PCIeDevices, NetworkAdapters, and StorageControllers.
Using the information collected, it will build an inventory table and print the information.


### Power/Reset

```
Expand Down
2 changes: 2 additions & 0 deletions redfish_utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# Copyright 2019 DMTF. All rights reserved.
# License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/master/LICENSE.md

from .inventory import get_system_inventory
from .inventory import print_system_inventory
from .messages import print_error_payload
from .messages import verify_response
from .sensors import get_sensors
Expand Down
278 changes: 278 additions & 0 deletions redfish_utilities/inventory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
"""
Inventory Module
File : inventory.py
Brief : This file contains the definitions and functionalities for scanning a
Redfish service for an inventory of components
"""

def get_system_inventory( context ):
"""
Walks a Redfish service for system component information, such as drives,
processors, and memory
Args:
context: The Redfish client object with an open session
Returns:
A list containing all system component information
"""

inventory_list = []

# Get the Service Root to find the Chassis Collection
service_root = context.get( "/redfish/v1/", None )
if "Chassis" not in service_root.dict:
# No Chassis Collection
return inventory_list

# Get the Chassis Collection and iterate through its collection
chassis_col = context.get( service_root.dict["Chassis"]["@odata.id"], None )
for chassis_member in chassis_col.dict["Members"]:
chassis = context.get( chassis_member["@odata.id"], None )

# Catalog Chassis itself
chassis_instance = {
"ChassisName": chassis.dict["Id"],
"Chassis": [],
"Processors": [],
"Memory": [],
"Drives": [],
"PCIeDevices": [],
"StorageControllers": [],
"NetworkAdapters": []
}
inventory_list.append( chassis_instance )
catalog_resource( chassis.dict, chassis_instance["Chassis"] )

# Catalog all Drives, PCIeDevices, NetworkAdapters, Systems, and ResourceBlocks in the Chassis
if "Links" in chassis.dict:
catalog_array( context, chassis.dict["Links"], "Drives", chassis_instance["Drives"] )
catalog_array( context, chassis.dict["Links"], "PCIeDevices", chassis_instance["PCIeDevices"] )
catalog_systems( context, chassis.dict["Links"], "ComputerSystems", chassis_instance )
catalog_collection( context, chassis.dict, "NetworkAdapters", chassis_instance["NetworkAdapters"] )

return inventory_list

def catalog_array( context, resource, name, inventory ):
"""
Catalogs an array of resources for the inventory list
Args:
context: The Redfish client object with an open session
resource: The resource with the array
name: The name of the property of the array
inventory: The inventory list to update
"""

if name in resource:
for member in resource[name]:
member_res = context.get( member["@odata.id"], None )
catalog_resource( member_res.dict, inventory )

def catalog_collection( context, resource, name, inventory ):
"""
Catalogs a collection of resources for the inventory list
Args:
context: The Redfish client object with an open session
resource: The resource with the collection
name: The name of the property of the collection
inventory: The inventory list to update
"""

if name in resource:
collection = context.get( resource[name]["@odata.id"], None )
for member in collection.dict["Members"]:
member_res = context.get( member["@odata.id"], None )
catalog_resource( member_res.dict, inventory )

def catalog_systems( context, resource, name, inventory ):
"""
Catalogs an array of systems for the inventory list
Args:
context: The Redfish client object with an open session
resource: The resource with the array of computer systems
name: The name of the property of the array
inventory: The inventory list to update
"""

if name in resource:
for system in resource[name]:
system_res = context.get( system["@odata.id"], None )

# Catalog all Processors, Memory, and PCIeDevices in the System
catalog_collection( context, system_res.dict, "Processors", inventory["Processors"] )
catalog_collection( context, system_res.dict, "Memory", inventory["Memory"] )
catalog_array( context, system_res.dict, "PCIeDevices", inventory["PCIeDevices"] )
catalog_simple_storage( context, system_res.dict, "SimpleStorage", inventory["Drives"] )
catalog_storage( context, system_res.dict, "Storage", inventory )

def catalog_simple_storage( context, resource, name, inventory ):
"""
Catalogs a collection of Simple Storage resources for the inventory list
Args:
context: The Redfish client object with an open session
resource: The resource with the collection
name: The name of the property of the collection
inventory: The inventory list to update
"""

if name in resource:
collection = context.get( resource[name]["@odata.id"], None )
for member in collection.dict["Members"]:
member_res = context.get( member["@odata.id"], None )
if "Devices" in member_res.dict:
for index, drive in enumerate( member_res.dict["Devices"] ):
drive["@odata.id"] = "{}#/Devices/{}".format( member_res.dict["@odata.id"], index )
drive["@odata.type"] = "#Drive.Drive"
drive["Id"] = drive["Name"]
catalog_resource( drive, inventory )

def catalog_storage( context, resource, name, inventory ):
"""
Catalogs a collection of Storage resources for the inventory list
Args:
context: The Redfish client object with an open session
resource: The resource with the collection
name: The name of the property of the collection
inventory: The inventory list to update
"""

if name in resource:
collection = context.get( resource[name]["@odata.id"], None )
for member in collection.dict["Members"]:
member_res = context.get( member["@odata.id"], None )
catalog_array( context, member_res.dict, "Drives", inventory["Drives"] )
if "StorageControllers" in member_res.dict:
for index, controller in enumerate( member_res.dict["StorageControllers"] ):
controller["@odata.type"] = "#StorageController.StorageController"
catalog_resource( controller, inventory["StorageControllers"] )

def catalog_resource( resource, inventory ):
"""
Catalogs a resource for the inventory list
Args:
resource: The resource to catalog
inventory: The inventory list to update
"""

# Scan the inventory to ensure this is a new entry
for item in inventory:
if item["Uri"] == resource["@odata.id"]:
return

resource_type = resource["@odata.type"].rsplit( "." )[-1]

location_prop = "Location"
if resource_type == "Drive":
location_prop = "PhysicalLocation"

catalog = {
"Uri": resource["@odata.id"],
"PartNumber": resource.get( "PartNumber", None ),
"SerialNumber": resource.get( "SerialNumber", None ),
"Manufacturer": resource.get( "Manufacturer", None ),
"Model": resource.get( "Model", None ),
"SKU": resource.get( "SKU", None ),
"AssetTag": resource.get( "AssetTag", None ),
"Label": resource.get( location_prop, {} ).get( "PartLocation", {} ).get( "ServiceLabel", None ),
"State": resource.get( "Status", {} ).get( "State", None ),
"Description": None
}

# If no label was found, build a default name
if catalog["Label"] is None:
catalog["Label"] = resource_type + ": " + resource["Id"]

# Build a string description of the component based on other properties
prop_list = []
if resource_type == "Chassis":
prop_list = [ "Model" ]
elif resource_type == "Processor":
if catalog["Model"] is not None:
prop_list = [ "Model" ]
else:
prop_list = [ "Manufacturer", "ProcessorArchitecture", "ProcessorType", "TotalCores", "MaxSpeedMHz" ]
elif resource_type == "Memory":
prop_list = [ "Manufacturer", "CapacityMiB", "MemoryDeviceType", "MemoryType" ]
elif resource_type == "Drive":
prop_list = [ "Manufacturer", "CapacityBytes", "Protocol", "MediaType" ]
elif resource_type == "PCIeDevice":
prop_list = [ "Manufacturer", "Model" ]
elif resource_type == "StorageController":
prop_list = [ "Manufacturer", "SpeedGbps", "SupportedDeviceProtocols" ]
elif resource_type == "NetworkAdapter":
prop_list = [ "Manufacturer", "Model" ]

# Based on the listed properties for the resource type, build the description string
if catalog["State"] != "Absent":
description_str = ""
for prop in prop_list:
if resource.get( prop, None ) is not None:
prop_val = resource[prop]
# Some properties require refinement
if prop == "TotalCores":
prop_val = str( prop_val ) + " Cores"
elif prop == "MaxSpeedMHz":
prop_val = "@ " + str( prop_val ) + "MHz"
elif prop == "CapacityMiB":
prop_val = str( prop_val ) + "MB"
elif prop == "CapacityBytes":
prop_val = str( int( prop_val / ( 2 ** 30 ) ) ) + "GB"
elif prop == "SpeedGbps":
prop_val = str( prop_val ) + "Gbps"
elif prop == "SupportedDeviceProtocols":
prop_val = prop_val.join( "/" ) + " Controller"
description_str = description_str + " " + prop_val
else:
# Some properties will have a default if not found
if prop == "MediaType":
description_str = description_str + " Drive"
elif prop == "SupportedDeviceProtocols":
description_str = description_str + " Storage Controller"
catalog["Description"] = description_str.strip()

inventory.append( catalog )

def print_system_inventory( inventory_list, details = False, skip_absent = False ):
"""
Prints the system inventory list into a table
Args:
inventory_list: The inventory list to print
details: True to print all of the detailed info
skip_absent: True to skip printing absent components
"""

inventory_line_format = " {:35s} | {}"
inventory_line_format_detail = " {:35s} | {}: {}"
inventory_line_format_empty = " {:35s} | Not Present"

# Go through each chassis instance
for chassis in inventory_list:
print( "'" + chassis["ChassisName"] + "' Inventory" )
print( inventory_line_format.format( "Name", "Description" ) )

# Go through each component type in the chassis
type_list = [ "Chassis", "Processors", "Memory", "Drives", "PCIeDevices", "StorageControllers", "NetworkAdapters" ]
for inv_type in type_list:
# Go through each component and prints its info
for item in chassis[inv_type]:
if item["State"] == "Absent":
if not skip_absent:
print( inventory_line_format_empty.format( item["Label"][:35] ) )
else:
print( inventory_line_format.format( item["Label"][:35], item["Description"] ) )

if details:
detail_list = [ "Manufacturer", "Model", "SKU", "PartNumber", "SerialNumber", "AssetTag" ]
for detail in detail_list:
if item[detail] is not None:
print( inventory_line_format_detail.format( "", detail, item[detail] ) )
print( "" )
2 changes: 1 addition & 1 deletion scripts/rf_boot_override
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ argget.add_argument( "--reset", "-reset", action = "store_true", help = "Signifi
args = argget.parse_args()

# Set up the Redfish object
redfish_obj = redfish.redfish_client( base_url = args.rhost, username = args.user, password = args.password, default_prefix = "/redfish/v1" )
redfish_obj = redfish.redfish_client( base_url = args.rhost, username = args.user, password = args.password )
redfish_obj.login( auth = "session" )

try:
Expand Down
2 changes: 1 addition & 1 deletion scripts/rf_power_reset
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ argget.add_argument( "--type", "-t", type = str, help = "The type of power/reset
args = argget.parse_args()

# Set up the Redfish object
redfish_obj = redfish.redfish_client( base_url = args.rhost, username = args.user, password = args.password, default_prefix = "/redfish/v1" )
redfish_obj = redfish.redfish_client( base_url = args.rhost, username = args.user, password = args.password )
redfish_obj.login( auth = "session" )

try:
Expand Down
2 changes: 1 addition & 1 deletion scripts/rf_sensor_list
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ argget.add_argument( "--rhost", "-r", type = str, required = True, help = "The a
args = argget.parse_args()

# Set up the Redfish object
redfish_obj = redfish.redfish_client( base_url = args.rhost, username = args.user, password = args.password, default_prefix = "/redfish/v1" )
redfish_obj = redfish.redfish_client( base_url = args.rhost, username = args.user, password = args.password )
redfish_obj.login( auth = "session" )

try:
Expand Down
40 changes: 40 additions & 0 deletions scripts/rf_sys_inventory
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#! /usr/bin/python
# Copyright Notice:
# Copyright 2019 DMTF. All rights reserved.
# License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/master/LICENSE.md

"""
Redfish System Inventory
File : rf_sys_inventory
Brief : This script uses the redfish_utilities module to dump system inventory
information
"""

import argparse
import redfish
import redfish_utilities

import json

# Get the input arguments
argget = argparse.ArgumentParser( description = "A tool to walk a Redfish service and list component information" )
argget.add_argument( "--user", "-u", type = str, required = True, help = "The user name for authentication" )
argget.add_argument( "--password", "-p", type = str, required = True, help = "The password for authentication" )
argget.add_argument( "--rhost", "-r", type = str, required = True, help = "The address of the Redfish service (with scheme)" )
argget.add_argument( "--details", "-details", action = "store_true", help = "Indicates if the full details of each component should be shown" )
argget.add_argument( "--noabsent", "-noabsent", action = "store_true", help = "Indicates if absent devices should be skipped" )
args = argget.parse_args()

# Set up the Redfish object
redfish_obj = redfish.redfish_client( base_url = args.rhost, username = args.user, password = args.password )
redfish_obj.login( auth = "session" )

try:
# Get and print the system inventory
inventory = redfish_utilities.get_system_inventory( redfish_obj )
redfish_utilities.print_system_inventory( inventory, details = args.details, skip_absent = args.noabsent )
finally:
# Log out
redfish_obj.logout()
Loading

0 comments on commit 4cc08d1

Please sign in to comment.