mirror of
https://github.com/davidallendj/magellan.git
synced 2025-12-20 03:27:03 -07:00
feat: enhance firmware update functionality and add BMC identification support
This commit is contained in:
parent
9ef3f86f70
commit
d8e47cd1a1
4 changed files with 157 additions and 22 deletions
|
|
@ -175,7 +175,7 @@ Note: If the `cache` flag is not set, `magellan` will use `/tmp/$USER/magellan.d
|
||||||
|
|
||||||
### Updating Firmware
|
### Updating Firmware
|
||||||
|
|
||||||
The `magellan` tool is capable of updating firmware with using the `update` subcommand via the Redfish API. This may sometimes necessary if some of the `collect` output is missing or is not including what is expected. The subcommand expects there to be a running HTTP/HTTPS server running that has an accessible URL path to the firmware download. Specify the URL with the `--firmware-path` flag and the firmware type with the `--component` flag with all the other usual arguments like in the example below:
|
The `magellan` tool is capable of updating firmware with using the `update` subcommand via the Redfish API. This may sometimes necessary if some of the `collect` output is missing or is not including what is expected. The subcommand expects there to be a running HTTP/HTTPS server running that has an accessible URL path to the firmware download. Specify the URL with the `--firmware-path` flag and the firmware type with the `--component` flag (optional) with all the other usual arguments like in the example below:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./magellan update 172.16.0.108:443 \
|
./magellan update 172.16.0.108:443 \
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
package magellan
|
package magellan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/OpenCHAMI/magellan/pkg/client"
|
"github.com/OpenCHAMI/magellan/pkg/client"
|
||||||
|
"github.com/stmcginnis/gofish"
|
||||||
|
"github.com/stmcginnis/gofish/redfish"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UpdateParams struct {
|
type UpdateParams struct {
|
||||||
|
|
@ -20,38 +21,47 @@ type UpdateParams struct {
|
||||||
// UpdateFirmwareRemote() uses 'gofish' to update the firmware of a BMC node.
|
// UpdateFirmwareRemote() uses 'gofish' to update the firmware of a BMC node.
|
||||||
// The function expects the firmware URL, firmware version, and component flags to be
|
// The function expects the firmware URL, firmware version, and component flags to be
|
||||||
// set from the CLI to perform a firmware update.
|
// set from the CLI to perform a firmware update.
|
||||||
|
// Example:
|
||||||
|
// ./magellan update https://192.168.23.40 --username root --password 0penBmc
|
||||||
|
// --firmware-url http://192.168.23.19:1337/obmc-phosphor-image.static.mtd.tar
|
||||||
|
// --scheme TFTP
|
||||||
|
//
|
||||||
|
// being:
|
||||||
|
// q.URI https://192.168.23.40
|
||||||
|
// q.TransferProtocol TFTP
|
||||||
|
// q.FirmwarePath http://192.168.23.19:1337/obmc-phosphor-image.static.mtd.tar
|
||||||
func UpdateFirmwareRemote(q *UpdateParams) error {
|
func UpdateFirmwareRemote(q *UpdateParams) error {
|
||||||
// parse URI to set up full address
|
// parse URI to set up full address
|
||||||
uri, err := url.ParseRequestURI(q.URI)
|
uri, err := url.ParseRequestURI(q.URI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse URI: %w", err)
|
return fmt.Errorf("failed to parse URI: %w", err)
|
||||||
}
|
}
|
||||||
uri.User = url.UserPassword(q.Username, q.Password)
|
|
||||||
|
|
||||||
// set up other vars
|
// Connect to the Redfish service using gofish (using insecure connection for this example)
|
||||||
updateUrl := fmt.Sprintf("%s/redfish/v1/UpdateService/Actions/SimpleUpdate", uri.String())
|
client, err := gofish.Connect(gofish.ClientConfig{Endpoint: uri.String(), Username: q.Username, Password: q.Password, Insecure: true})
|
||||||
headers := map[string]string{
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"cache-control": "no-cache",
|
|
||||||
}
|
|
||||||
b := map[string]any{
|
|
||||||
"UpdateComponent": q.Component, // BMC, BIOS
|
|
||||||
"TransferProtocol": q.TransferProtocol,
|
|
||||||
"ImageURI": q.FirmwarePath,
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(b)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal data: %v", err)
|
return fmt.Errorf("failed to connect to Redfish service: %w", err)
|
||||||
}
|
}
|
||||||
res, body, err := client.MakeRequest(nil, updateUrl, "POST", data, headers)
|
defer client.Logout()
|
||||||
|
|
||||||
|
// Retrieve the UpdateService from the Redfish client
|
||||||
|
updateService, err := client.Service.UpdateService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("something went wrong: %v", err)
|
return fmt.Errorf("failed to get update service: %w", err)
|
||||||
} else if res == nil {
|
|
||||||
return fmt.Errorf("no response returned (url: %s)", updateUrl)
|
|
||||||
}
|
}
|
||||||
if len(body) > 0 {
|
|
||||||
fmt.Printf("%d: %v\n", res.StatusCode, string(body))
|
// Build the update request payload
|
||||||
|
req := redfish.SimpleUpdateParameters{
|
||||||
|
ImageURI: q.FirmwarePath,
|
||||||
|
TransferProtocol: redfish.TransferProtocolType(q.TransferProtocol),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execute the SimpleUpdate action
|
||||||
|
err = updateService.SimpleUpdate(&req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("firmware update failed: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Firmware update initiated successfully.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
69
pkg/crawler/identify.go
Normal file
69
pkg/crawler/identify.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package crawler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/stmcginnis/gofish"
|
||||||
|
"github.com/stmcginnis/gofish/redfish"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BMCInfo represents relevant information about a BMC
|
||||||
|
type BMCInfo struct {
|
||||||
|
Manufacturer string `json:"manufacturer"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
SerialNumber string `json:"serial_number"`
|
||||||
|
FirmwareVersion string `json:"firmware_version"`
|
||||||
|
ManagerType string `json:"manager_type"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBMC checks if a given Manager is a BMC based on its type and associations
|
||||||
|
func IsBMC(manager *redfish.Manager) bool {
|
||||||
|
if manager == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid BMC types in Redfish
|
||||||
|
bmcTypes := map[string]bool{
|
||||||
|
"BMC": true,
|
||||||
|
"ManagementController": true, // Some BMCs use this type
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ManagerType matches a BMC type
|
||||||
|
if !bmcTypes[string(manager.ManagerType)] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false // Otherwise, it's likely a chassis manager or other device
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBMCInfo retrieves details of all available BMCs
|
||||||
|
func GetBMCInfo(client *gofish.APIClient) ([]BMCInfo, error) {
|
||||||
|
var bmcList []BMCInfo
|
||||||
|
|
||||||
|
// Retrieve all managers (BMCs and other managers)
|
||||||
|
managers, err := client.Service.Managers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve managers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through each manager and collect BMC details
|
||||||
|
for _, manager := range managers {
|
||||||
|
if !IsBMC(manager) {
|
||||||
|
continue // Skip if it's not a BMC
|
||||||
|
}
|
||||||
|
|
||||||
|
bmc := BMCInfo{
|
||||||
|
Manufacturer: manager.Manufacturer,
|
||||||
|
Model: manager.Model,
|
||||||
|
SerialNumber: manager.SerialNumber,
|
||||||
|
FirmwareVersion: manager.FirmwareVersion,
|
||||||
|
ManagerType: string(manager.ManagerType), // Convert ManagerType to string
|
||||||
|
UUID: manager.UUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
bmcList = append(bmcList, bmc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bmcList, nil
|
||||||
|
}
|
||||||
|
|
@ -130,6 +130,24 @@ func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CrawlBMCForSystems pulls BMC manager information.
|
// CrawlBMCForSystems pulls BMC manager information.
|
||||||
|
// CrawlBMCForManagers connects to a BMC (Baseboard Management Controller) using the provided configuration,
|
||||||
|
// retrieves the ServiceRoot, and then fetches the list of managers from the ServiceRoot.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - config: A CrawlerConfig struct containing the URI, username, password, and other connection details.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - []Manager: A slice of Manager structs representing the managers retrieved from the BMC.
|
||||||
|
// - error: An error object if any error occurs during the connection or retrieval process.
|
||||||
|
//
|
||||||
|
// The function performs the following steps:
|
||||||
|
// 1. Initializes a gofish client with the provided configuration.
|
||||||
|
// 2. Attempts to connect to the BMC using the gofish client.
|
||||||
|
// 3. Handles specific connection errors such as 404 (ServiceRoot not found) and 401 (authentication failed).
|
||||||
|
// 4. Logs out from the client after the operations are completed.
|
||||||
|
// 5. Retrieves the ServiceRoot from the connected BMC.
|
||||||
|
// 6. Fetches the list of managers from the ServiceRoot.
|
||||||
|
// 7. Returns the list of managers and any error encountered during the process.
|
||||||
func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) {
|
func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) {
|
||||||
// initialize gofish client
|
// initialize gofish client
|
||||||
var managers []Manager
|
var managers []Manager
|
||||||
|
|
@ -165,6 +183,27 @@ func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) {
|
||||||
return walkManagers(rf_managers, config.URI)
|
return walkManagers(rf_managers, config.URI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// walkSystems processes a list of Redfish computer systems and their associated chassis,
|
||||||
|
// and returns a list of inventory details for each system.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - rf_systems: A slice of pointers to redfish.ComputerSystem objects representing the computer systems to be processed.
|
||||||
|
// - rf_chassis: A pointer to a redfish.Chassis object representing the chassis associated with the computer systems.
|
||||||
|
// - baseURI: A string representing the base URI for constructing resource URIs.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - A slice of InventoryDetail objects containing detailed information about each computer system.
|
||||||
|
// - An error if any issues occur while processing the computer systems or their associated resources.
|
||||||
|
//
|
||||||
|
// The function performs the following steps:
|
||||||
|
// 1. Iterates over each computer system in rf_systems.
|
||||||
|
// 2. Constructs an InventoryDetail object for each computer system, populating fields such as URI, UUID, Name, Manufacturer, SystemType, Model, Serial, BiosVersion, PowerState, ProcessorCount, ProcessorType, and MemoryTotal.
|
||||||
|
// 3. If rf_chassis is not nil, populates additional chassis-related fields in the InventoryDetail object.
|
||||||
|
// 4. Retrieves and processes Ethernet interfaces for each computer system, adding them to the EthernetInterfaces field of the InventoryDetail object.
|
||||||
|
// 5. Retrieves and processes Network interfaces and their associated network adapters for each computer system, adding them to the NetworkInterfaces field of the InventoryDetail object.
|
||||||
|
// 6. Processes trusted modules for each computer system, adding them to the TrustedModules field of the InventoryDetail object.
|
||||||
|
// 7. Appends the populated InventoryDetail object to the systems slice.
|
||||||
|
// 8. Returns the systems slice and any error encountered during processing.
|
||||||
func walkSystems(rf_systems []*redfish.ComputerSystem, rf_chassis *redfish.Chassis, baseURI string) ([]InventoryDetail, error) {
|
func walkSystems(rf_systems []*redfish.ComputerSystem, rf_chassis *redfish.Chassis, baseURI string) ([]InventoryDetail, error) {
|
||||||
systems := []InventoryDetail{}
|
systems := []InventoryDetail{}
|
||||||
for _, rf_computersystem := range rf_systems {
|
for _, rf_computersystem := range rf_systems {
|
||||||
|
|
@ -253,6 +292,23 @@ func walkSystems(rf_systems []*redfish.ComputerSystem, rf_chassis *redfish.Chass
|
||||||
return systems, nil
|
return systems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// walkManagers processes a list of Redfish managers and extracts relevant information
|
||||||
|
// to create a slice of Manager objects.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// rf_managers - A slice of pointers to redfish.Manager objects representing the Redfish managers to be processed.
|
||||||
|
// baseURI - A string representing the base URI to be used for constructing URIs for the managers and their Ethernet interfaces.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// A slice of Manager objects containing the extracted information from the provided Redfish managers.
|
||||||
|
// An error if any issues occur while retrieving Ethernet interfaces from the managers.
|
||||||
|
//
|
||||||
|
// The function iterates over each Redfish manager, retrieves its Ethernet interfaces,
|
||||||
|
// and constructs a Manager object with the relevant details, including Ethernet interface information.
|
||||||
|
// If an error occurs while retrieving Ethernet interfaces, the function logs the error and returns the managers
|
||||||
|
// collected so far along with the error.
|
||||||
func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, error) {
|
func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, error) {
|
||||||
var managers []Manager
|
var managers []Manager
|
||||||
for _, rf_manager := range rf_managers {
|
for _, rf_manager := range rf_managers {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue