Skip to content

Commit

Permalink
Merge pull request #142 from DMTF/Assembly-Tool
Browse files Browse the repository at this point in the history
Added 'rf_assembly.py' to manage Assembly resources on a service
  • Loading branch information
mraineri authored Mar 1, 2024
2 parents f216923 + bee98ae commit b1decdb
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ You may install the external modules by running:
* [Licenses (rf_licenses.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_licenses.md)
* [Certificates (rf_certificates.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_certificates.md)
* [Diagnostic Data (rf_diagnostic_data.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_diagnostic_data.md)
* [Assembly (rf_assembly.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_assembly.md)
* [Power Equipment (rf_power_equipment.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_power_equipment.md)
* [Raw Request (rf_raw_request.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_raw_request.md)
* [Test Event Listener (rf_test_event_listener.py)](https://github.com/DMTF/Redfish-Tacklebox/blob/main/docs/rf_test_event_listener.md)
Expand Down
110 changes: 110 additions & 0 deletions docs/rf_assembly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Assembly (rf_assembly.py)

Copyright 2019-2024 DMTF. All rights reserved.

## About

A tool to manage assemblies on a Redfish service.

## Usage

```
usage: rf_assembly.py [-h] --user USER --password PASSWORD --rhost RHOST
--assembly ASSEMBLY [--index INDEX] [--debug]
{info,download,upload} ...
A tool to manage assemblies on a Redfish service
positional arguments:
{info,download,upload}
info Displays information about the an assembly
download Downloads assembly data to a file
upload Uploads assembly data from a file
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)
--assembly ASSEMBLY, -a ASSEMBLY
The URI of the target assembly
optional arguments:
-h, --help show this help message and exit
--index INDEX, -i INDEX
The target assembly index
--debug Creates debug file showing HTTP traces and exceptions
```

### Info

Displays information about the an assembly.

```
usage: rf_assembly.py info [-h]
optional arguments:
-h, --help show this help message and exit
```

The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments.
It will then get the assembly information from the URI specified by the *assembly* argument and displays its information.

Example:

```
$ rf_assembly.py -u root -p root -r https://192.168.1.100 -a /redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1/Assembly info
0 | Contoso Power Supply
| Model: 345TTT
| PartNumber: 923943
| SerialNumber: 345394834
| Producer: Contoso Supply Co.
| Vendor: Contoso
| ProductionDate: 2017-04-01T14:55:33+03:00
```

### Download

Downloads assembly data to a file.

```
usage: rf_assembly.py download [-h] --file FILE
required arguments:
--file FILE, -f FILE The file, and optional path, to save the assembly data
optional arguments:
-h, --help show this help message and exit
```

The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments.
It will then get the assembly information from the URI specified by the *assembly* argument and download the binary data contents to the file specified by the *file* argument.

```
$ rf_assembly.py -u root -p root -r https://192.168.1.100 -a /redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1/Assembly download -f data.bin
Saving data to 'data.bin'...
```

### Upload

Uploads assembly data from a file.

```
usage: rf_assembly.py upload [-h] --file FILE
required arguments:
--file FILE, -f FILE The file, and optional path, containing the assembly
data to upload
optional arguments:
-h, --help show this help message and exit
```

The tool will log into the service specified by the *rhost* argument using the credentials provided by the *user* and *password* arguments.
It will then get the assembly information from the URI specified by the *assembly* argument and upload the contents of the file specified by the *file* argument to the binary data.

```
$ rf_assembly.py -u root -p root -r https://192.168.1.100 -a /redfish/v1/Chassis/1U/PowerSubsystem/PowerSupplies/Bay1/Assembly upload -f data.bin
Writing data from 'data.bin'...
```
6 changes: 5 additions & 1 deletion redfish_utilities/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
#! /usr/bin/python
# Copyright Notice:
# Copyright 2019-2023 DMTF. All rights reserved.
# Copyright 2019-2024 DMTF. All rights reserved.
# License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md

from .accounts import get_users
from .accounts import print_users
from .accounts import add_user
from .accounts import delete_user
from .accounts import modify_user
from .assembly import get_assembly
from .assembly import print_assembly
from .assembly import download_assembly
from .assembly import upload_assembly
from .certificates import get_all_certificates
from .certificates import print_certificates
from .certificates import get_generate_csr_info
Expand Down
152 changes: 152 additions & 0 deletions redfish_utilities/assembly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#! /usr/bin/python
# Copyright Notice:
# Copyright 2019-2024 DMTF. All rights reserved.
# License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md

"""
Assembly Module
File : assembly.py
Brief : This file contains the definitions and functionalities for managing
assemblies on a Redfish service
"""

from .messages import verify_response

class RedfishAssemblyNotFoundError( Exception ):
"""
Raised when an assembly index cannot be found
"""
pass

class RedfishAssemblyNoBinaryDataError( Exception ):
"""
Raised when an assembly index does not contain binary data
"""
pass

def get_assembly( context, uri ):
"""
Collects assembly information from a Redfish service
Args:
context: The Redfish client object with an open session
uri: The URI of the assembly to get
Returns:
A list containing all assemblies from the URI
"""

# Get the assembly
assembly = context.get( uri )
verify_response( assembly )
return assembly.dict.get( "Assemblies", [] )

def print_assembly( assemblies, index = None ):
"""
Prints assembly information into a table
Args:
assemblies: An array of assembly information to print
index: If specified, prints only the desired index
"""

assembly_format_header = " {:5s} | {} {}"
assembly_format = " {:5s} | {}: {}"
assembly_properties = [ "Model", "PartNumber", "SparePartNumber", "SKU", "SerialNumber", "Producer", "Vendor",
"ProductionDate", "Version", "EngineeringChangeLevel" ]

# If an index is specified, isolate to the one index
if index is not None:
if index < 0 or index >= len( assemblies ):
raise RedfishAssemblyNotFoundError( "Assembly contains {} entries; index {} is not valid".format( len( assemblies ), index ) )
assemblies = [ assemblies[index] ]

# Go through each assembly
for assembly in assemblies:
# Print the heading
heading_details = []
state = assembly.get( "Status", {} ).get( "State" )
if state:
heading_details.append( state )
health = assembly.get( "Status", {} ).get( "Health" )
if health:
heading_details.append( health )
heading_details = ", ".join( heading_details )
if len( heading_details ) != 0:
heading_details = "(" + heading_details + ")"
print( assembly_format_header.format( assembly["MemberId"], assembly["Name"], heading_details ) )

# Print any of the found properties
for property in assembly_properties:
if property in assembly:
print( assembly_format.format( "", property, assembly[property] ) )

def download_assembly( context, assemblies, filepath, index = None ):
"""
Downloads the binary data of an assembly to a file
Args:
context: The Redfish client object with an open session
assemblies: An array of assembly information
filepath: The filepath to download the binary data
index: The index into the assemblies array to download; if None, perform on index 0 if there's only 1 assembly
"""

# Get the binary data URI
binary_data_uri = get_assembly_binary_data_uri( assemblies, index )

# Download the data and save it
response = context.get( binary_data_uri )
verify_response( response )
with open( filepath, "wb" ) as binary_file:
binary_file.write( response.read )

def upload_assembly( context, assemblies, filepath, index = None ):
"""
Uploads the binary data of a file to an assembly
Args:
context: The Redfish client object with an open session
assemblies: An array of assembly information
filepath: The filepath of the binary data to upload
index: The index into the assemblies array to upload; if None, perform on index 0 if there's only 1 assembly
"""

# Get the binary data URI
binary_data_uri = get_assembly_binary_data_uri( assemblies, index )

# Upload the binary data
with open( filepath, "rb" ) as binary_file:
data = binary_file.read()
response = context.put( binary_data_uri, body = data )
verify_response( response )

def get_assembly_binary_data_uri( assemblies, index = None ):
"""
Locates the binary data URI for a target assembly
Args:
assemblies: An array of assembly information
index: The index into the assemblies array to download; if None, perform on index 0 if there's only 1 assembly
Returns:
A string containing the binary data URI
"""

# If an index is specified, isolate to the one index
if index is None:
index = 0
if len( assemblies ) != 1:
raise RedfishAssemblyNotFoundError( "Assembly contains {} entries; an index needs to be specified".format( len( assemblies ) ) )
else:
if index < 0 or index >= len( assemblies ):
raise RedfishAssemblyNotFoundError( "Assembly contains {} entries; index {} is not valid".format( len( assemblies ), index ) )

# Get the binary data URI
binary_data_uri = assemblies[index].get( "BinaryDataURI" )
if binary_data_uri is None:
# No binary data
raise RedfishAssemblyNoBinaryDataError( "Assembly index {} does not contain binary data".format( index ) )
return binary_data_uri
79 changes: 79 additions & 0 deletions scripts/rf_assembly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#! /usr/bin/python
# Copyright Notice:
# Copyright 2019-2024 DMTF. All rights reserved.
# License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Tacklebox/blob/main/LICENSE.md

"""
Redfish Assembly
File : rf_assembly.py
Brief : This script uses the redfish_utilities module to manage assemblies
"""

import argparse
import datetime
import logging
import redfish
import redfish_utilities
import traceback
import sys
from redfish.messages import RedfishPasswordChangeRequiredError

# Get the input arguments
argget = argparse.ArgumentParser( description = "A tool to manage assemblies on a Redfish service" )
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( "--assembly", "-a", type = str, required = True, help = "The URI of the target assembly" )
argget.add_argument( "--index", "-i", type = int, help = "The target assembly index" )
argget.add_argument( "--debug", action = "store_true", help = "Creates debug file showing HTTP traces and exceptions" )
subparsers = argget.add_subparsers( dest = "command" )
info_argget = subparsers.add_parser( "info", help = "Displays information about the an assembly" )
download_argget = subparsers.add_parser( "download", help = "Downloads assembly data to a file" )
download_argget.add_argument( "--file", "-f", type = str, required = True, help = "The file, and optional path, to save the assembly data" )
upload_argget = subparsers.add_parser( "upload", help = "Uploads assembly data from a file" )
upload_argget.add_argument( "--file", "-f", type = str, required = True, help = "The file, and optional path, containing the assembly data to upload" )
args = argget.parse_args()

if args.index and args.index < 0:
print( "rf_assembly.py: error: the assembly index cannot be negative" )
sys.exit( 1 )

if args.debug:
log_file = "rf_assembly-{}.log".format( datetime.datetime.now().strftime( "%Y-%m-%d-%H%M%S" ) )
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
logger = redfish.redfish_logger( log_file, log_format, logging.DEBUG )
logger.info( "rf_assembly Trace" )

# Set up the Redfish object
redfish_obj = None
try:
redfish_obj = redfish.redfish_client( base_url = args.rhost, username = args.user, password = args.password, timeout = 15, max_retry = 3 )
redfish_obj.login( auth = "session" )
except RedfishPasswordChangeRequiredError as e:
redfish_utilities.print_password_change_required_and_logout( redfish_obj, args )
sys.exit( 1 )
except Exception as e:
raise

exit_code = 0
try:
assembly_info = redfish_utilities.get_assembly( redfish_obj, args.assembly )
if args.command == "download":
print( "Saving data to '{}'...".format( args.file ) )
redfish_utilities.download_assembly( redfish_obj, assembly_info, args.file, args.index )
elif args.command == "upload":
print( "Writing data from '{}'...".format( args.file) )
redfish_utilities.upload_assembly( redfish_obj, assembly_info, args.file, args.index )
else:
redfish_utilities.print_assembly( assembly_info, args.index )
except Exception as e:
if args.debug:
logger.error( "Caught exception:\n\n{}\n".format( traceback.format_exc() ) )
exit_code = 1
print( e )
finally:
# Log out
redfish_utilities.logout( redfish_obj )
sys.exit( exit_code )
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def run(self):
packages = [ "redfish_utilities" ],
scripts = [
"scripts/rf_accounts.py",
"scripts/rf_assembly.py",
"scripts/rf_bios_settings.py",
"scripts/rf_boot_override.py",
"scripts/rf_certificates.py",
Expand Down

0 comments on commit b1decdb

Please sign in to comment.