mirror of
https://github.com/davidallendj/magellan.git
synced 2025-12-20 11:37:01 -07:00
Merge pull request #62 from OpenCHAMI/manager-data
Add manager information from node BMC
This commit is contained in:
commit
3417c6c29e
4 changed files with 198 additions and 19 deletions
|
|
@ -35,7 +35,7 @@ var crawlCmd = &cobra.Command{
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
systems, err := crawler.CrawlBMC(crawler.CrawlerConfig{
|
systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{
|
||||||
URI: args[0],
|
URI: args[0],
|
||||||
Username: cmd.Flag("username").Value.String(),
|
Username: cmd.Flag("username").Value.String(),
|
||||||
Password: cmd.Flag("password").Value.String(),
|
Password: cmd.Flag("password").Value.String(),
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -21,7 +22,8 @@ import (
|
||||||
|
|
||||||
"github.com/Cray-HPE/hms-xname/xnames"
|
"github.com/Cray-HPE/hms-xname/xnames"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
_ "github.com/stmcginnis/gofish"
|
"github.com/stmcginnis/gofish"
|
||||||
|
"github.com/stmcginnis/gofish/redfish"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,12 +43,13 @@ type CollectParams struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the main function used to collect information from the BMC nodes via Redfish.
|
// This is the main function used to collect information from the BMC nodes via Redfish.
|
||||||
|
// The results of the collect are stored in a cache specified with the `--cache` flag.
|
||||||
// The function expects a list of hosts found using the `ScanForAssets()` function.
|
// The function expects a list of hosts found using the `ScanForAssets()` function.
|
||||||
//
|
//
|
||||||
// Requests can be made to several of the nodes using a goroutine by setting the q.Concurrency
|
// Requests can be made to several of the nodes using a goroutine by setting the q.Concurrency
|
||||||
// property value between 1 and 255.
|
// property value between 1 and 10000.
|
||||||
func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error {
|
func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error {
|
||||||
// check for available probe states
|
// check for available remote assets found from scan
|
||||||
if assets == nil {
|
if assets == nil {
|
||||||
return fmt.Errorf("no assets found")
|
return fmt.Errorf("no assets found")
|
||||||
}
|
}
|
||||||
|
|
@ -109,14 +112,23 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error {
|
||||||
offset += 1
|
offset += 1
|
||||||
|
|
||||||
// crawl BMC node to fetch inventory data via Redfish
|
// crawl BMC node to fetch inventory data via Redfish
|
||||||
systems, err := crawler.CrawlBMC(crawler.CrawlerConfig{
|
var (
|
||||||
URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port),
|
systems []crawler.InventoryDetail
|
||||||
Username: params.Username,
|
managers []crawler.Manager
|
||||||
Password: params.Password,
|
config = crawler.CrawlerConfig{
|
||||||
Insecure: true,
|
URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port),
|
||||||
})
|
Username: params.Username,
|
||||||
|
Password: params.Password,
|
||||||
|
Insecure: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
systems, err := crawler.CrawlBMCForSystems(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("failed to crawl BMC")
|
log.Error().Err(err).Msg("failed to crawl BMC for systems")
|
||||||
|
}
|
||||||
|
managers, err = crawler.CrawlBMCForManagers(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to crawl BMC for managers")
|
||||||
}
|
}
|
||||||
|
|
||||||
// data to be sent to smd
|
// data to be sent to smd
|
||||||
|
|
@ -129,9 +141,20 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error {
|
||||||
"MACRequired": true,
|
"MACRequired": true,
|
||||||
"RediscoverOnUpdate": false,
|
"RediscoverOnUpdate": false,
|
||||||
"Systems": systems,
|
"Systems": systems,
|
||||||
|
"Managers": managers,
|
||||||
"SchemaVersion": 1,
|
"SchemaVersion": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optionally, add the MACAddr property if we find a matching IP
|
||||||
|
// from the correct ethernet interface
|
||||||
|
mac, err := FindMACAddressWithIP(config, net.ParseIP(sr.Host))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Msgf("failed to find MAC address with IP '%s'", sr.Host)
|
||||||
|
}
|
||||||
|
if mac != "" {
|
||||||
|
data["MACAddr"] = mac
|
||||||
|
}
|
||||||
|
|
||||||
// create and set headers for request
|
// create and set headers for request
|
||||||
headers := client.HTTPHeader{}
|
headers := client.HTTPHeader{}
|
||||||
headers.Authorization(params.AccessToken)
|
headers.Authorization(params.AccessToken)
|
||||||
|
|
@ -220,3 +243,75 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindMACAddressWithIP() returns the MAC address of an ethernet interface with
|
||||||
|
// a matching IPv4Address. Returns an empty string and error if there are no matches
|
||||||
|
// found.
|
||||||
|
func FindMACAddressWithIP(config crawler.CrawlerConfig, targetIP net.IP) (string, error) {
|
||||||
|
// get the managers to find the BMC MAC address compared with IP
|
||||||
|
//
|
||||||
|
// NOTE: Since we don't have a RedfishEndpoint type abstraction in
|
||||||
|
// magellan and the crawler crawls for systems information, it
|
||||||
|
// may just make more sense to get the managers directly via
|
||||||
|
// gofish (at least for now). If there's a need for grabbing more
|
||||||
|
// manager information in the future, we can move the logic into
|
||||||
|
// the crawler.
|
||||||
|
client, err := gofish.Connect(gofish.ClientConfig{
|
||||||
|
Endpoint: config.URI,
|
||||||
|
Username: config.Username,
|
||||||
|
Password: config.Password,
|
||||||
|
Insecure: config.Insecure,
|
||||||
|
BasicAuth: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if strings.HasPrefix(err.Error(), "404:") {
|
||||||
|
err = fmt.Errorf("no ServiceRoot found. This is probably not a BMC: %s", config.URI)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(err.Error(), "401:") {
|
||||||
|
err = fmt.Errorf("authentication failed. Check your username and password: %s", config.URI)
|
||||||
|
}
|
||||||
|
event := log.Error()
|
||||||
|
event.Err(err)
|
||||||
|
event.Msg("failed to connect to BMC")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer client.Logout()
|
||||||
|
|
||||||
|
var (
|
||||||
|
rf_service = client.GetService()
|
||||||
|
rf_managers []*redfish.Manager
|
||||||
|
)
|
||||||
|
rf_managers, err = rf_service.Managers()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get managers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the manager with the same IP address of the BMC to get
|
||||||
|
// it's MAC address from its EthernetInterface
|
||||||
|
for _, manager := range rf_managers {
|
||||||
|
eths, err := manager.EthernetInterfaces()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("failed to get ethernet interfaces from manager '%s'", manager.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, eth := range eths {
|
||||||
|
// compare the ethernet interface IP with argument
|
||||||
|
for _, ip := range eth.IPv4Addresses {
|
||||||
|
if ip.Address == targetIP.String() {
|
||||||
|
// we found matching IP address so return the ethernet interface MAC
|
||||||
|
return eth.MACAddress, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// do the same thing as above, but with static IP addresses
|
||||||
|
for _, ip := range eth.IPv4StaticAddresses {
|
||||||
|
if ip.Address == targetIP.String() {
|
||||||
|
return eth.MACAddress, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no matches found, so go to next ethernet interface
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no matches found, so return an empty string
|
||||||
|
return "", fmt.Errorf("no ethernet interfaces found with IP address")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,17 @@ type NetworkInterface struct {
|
||||||
Adapter NetworkAdapter `json:"adapter,omitempty"` // Adapter of the interface
|
Adapter NetworkAdapter `json:"adapter,omitempty"` // Adapter of the interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
URI string `json:"uri,omitempty"`
|
||||||
|
UUID string `json:"uuid,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
Model string `json:"model,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
FirmwareVersion string `json:"firmware_version,omitempty"`
|
||||||
|
EthernetInterfaces []EthernetInterface `json:"ethernet_interfaces,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type InventoryDetail struct {
|
type InventoryDetail struct {
|
||||||
URI string `json:"uri,omitempty"` // URI of the BMC
|
URI string `json:"uri,omitempty"` // URI of the BMC
|
||||||
UUID string `json:"uuid,omitempty"` // UUID of Node
|
UUID string `json:"uuid,omitempty"` // UUID of Node
|
||||||
|
|
@ -65,9 +76,12 @@ type InventoryDetail struct {
|
||||||
Chassis_Model string `json:"chassis_model,omitempty"` // Model of the Chassis
|
Chassis_Model string `json:"chassis_model,omitempty"` // Model of the Chassis
|
||||||
}
|
}
|
||||||
|
|
||||||
// CrawlBMC pulls all pertinent information from a BMC. It accepts a CrawlerConfig and returns a list of InventoryDetail structs.
|
// CrawlBMCForSystems pulls all pertinent information from a BMC. It accepts a CrawlerConfig and returns a list of InventoryDetail structs.
|
||||||
func CrawlBMC(config CrawlerConfig) ([]InventoryDetail, error) {
|
func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) {
|
||||||
var systems []InventoryDetail
|
var (
|
||||||
|
systems []InventoryDetail
|
||||||
|
rf_systems []*redfish.ComputerSystem
|
||||||
|
)
|
||||||
// initialize gofish client
|
// initialize gofish client
|
||||||
client, err := gofish.Connect(gofish.ClientConfig{
|
client, err := gofish.Connect(gofish.ClientConfig{
|
||||||
Endpoint: config.URI,
|
Endpoint: config.URI,
|
||||||
|
|
@ -94,8 +108,6 @@ func CrawlBMC(config CrawlerConfig) ([]InventoryDetail, error) {
|
||||||
rf_service := client.GetService()
|
rf_service := client.GetService()
|
||||||
log.Info().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion)
|
log.Info().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion)
|
||||||
|
|
||||||
var rf_systems []*redfish.ComputerSystem
|
|
||||||
|
|
||||||
// Nodes are sometimes only found under Chassis, but they should be found under Systems.
|
// Nodes are sometimes only found under Chassis, but they should be found under Systems.
|
||||||
rf_chassis, err := rf_service.Chassis()
|
rf_chassis, err := rf_service.Chassis()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -114,8 +126,43 @@ func CrawlBMC(config CrawlerConfig) ([]InventoryDetail, error) {
|
||||||
}
|
}
|
||||||
log.Info().Msgf("found %d systems in ServiceRoot", len(rf_root_systems))
|
log.Info().Msgf("found %d systems in ServiceRoot", len(rf_root_systems))
|
||||||
rf_systems = append(rf_systems, rf_root_systems...)
|
rf_systems = append(rf_systems, rf_root_systems...)
|
||||||
systems, err = walkSystems(rf_systems, nil, config.URI)
|
return walkSystems(rf_systems, nil, config.URI)
|
||||||
return systems, err
|
}
|
||||||
|
|
||||||
|
// CrawlBMCForSystems pulls BMC manager information.
|
||||||
|
func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) {
|
||||||
|
// initialize gofish client
|
||||||
|
var managers []Manager
|
||||||
|
client, err := gofish.Connect(gofish.ClientConfig{
|
||||||
|
Endpoint: config.URI,
|
||||||
|
Username: config.Username,
|
||||||
|
Password: config.Password,
|
||||||
|
Insecure: config.Insecure,
|
||||||
|
BasicAuth: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if strings.HasPrefix(err.Error(), "404:") {
|
||||||
|
err = fmt.Errorf("no ServiceRoot found. This is probably not a BMC: %s", config.URI)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(err.Error(), "401:") {
|
||||||
|
err = fmt.Errorf("authentication failed. Check your username and password: %s", config.URI)
|
||||||
|
}
|
||||||
|
event := log.Error()
|
||||||
|
event.Err(err)
|
||||||
|
event.Msg("failed to connect to BMC")
|
||||||
|
return managers, err
|
||||||
|
}
|
||||||
|
defer client.Logout()
|
||||||
|
|
||||||
|
// Obtain the ServiceRoot
|
||||||
|
rf_service := client.GetService()
|
||||||
|
log.Info().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion)
|
||||||
|
|
||||||
|
rf_managers, err := rf_service.Managers()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to get managers from ServiceRoot")
|
||||||
|
}
|
||||||
|
return walkManagers(rf_managers, config.URI)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|
@ -200,7 +247,44 @@ func walkSystems(rf_systems []*redfish.ComputerSystem, rf_chassis *redfish.Chass
|
||||||
for _, rf_trustedmodule := range rf_computersystem.TrustedModules {
|
for _, rf_trustedmodule := range rf_computersystem.TrustedModules {
|
||||||
system.TrustedModules = append(system.TrustedModules, fmt.Sprintf("%s %s", rf_trustedmodule.InterfaceType, rf_trustedmodule.FirmwareVersion))
|
system.TrustedModules = append(system.TrustedModules, fmt.Sprintf("%s %s", rf_trustedmodule.InterfaceType, rf_trustedmodule.FirmwareVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
systems = append(systems, system)
|
systems = append(systems, system)
|
||||||
}
|
}
|
||||||
return systems, nil
|
return systems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, error) {
|
||||||
|
var managers []Manager
|
||||||
|
for _, rf_manager := range rf_managers {
|
||||||
|
rf_ethernetinterfaces, err := rf_manager.EthernetInterfaces()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to get ethernet interfaces from manager")
|
||||||
|
return managers, err
|
||||||
|
}
|
||||||
|
var ethernet_interfaces []EthernetInterface
|
||||||
|
for _, rf_ethernetinterface := range rf_ethernetinterfaces {
|
||||||
|
if len(rf_ethernetinterface.IPv4Addresses) <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ethernet_interfaces = append(ethernet_interfaces, EthernetInterface{
|
||||||
|
URI: baseURI + rf_ethernetinterface.ODataID,
|
||||||
|
MAC: rf_ethernetinterface.MACAddress,
|
||||||
|
Name: rf_ethernetinterface.Name,
|
||||||
|
Description: rf_ethernetinterface.Description,
|
||||||
|
Enabled: rf_ethernetinterface.InterfaceEnabled,
|
||||||
|
IP: rf_ethernetinterface.IPv4Addresses[0].Address,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
managers = append(managers, Manager{
|
||||||
|
URI: baseURI + "/redfish/v1/Managers/" + rf_manager.ID,
|
||||||
|
UUID: rf_manager.UUID,
|
||||||
|
Name: rf_manager.Name,
|
||||||
|
Description: rf_manager.Description,
|
||||||
|
Model: rf_manager.Model,
|
||||||
|
Type: string(rf_manager.ManagerType),
|
||||||
|
FirmwareVersion: rf_manager.FirmwareVersion,
|
||||||
|
EthernetInterfaces: ethernet_interfaces,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return managers, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ func TestExpectedOutput(t *testing.T) {
|
||||||
t.Fatalf("failed while waiting for emulator: %v", err)
|
t.Fatalf("failed while waiting for emulator: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
systems, err := crawler.CrawlBMC(
|
systems, err := crawler.CrawlBMCForSystems(
|
||||||
crawler.CrawlerConfig{
|
crawler.CrawlerConfig{
|
||||||
URI: *host,
|
URI: *host,
|
||||||
Username: *username,
|
Username: *username,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue