collect: added MACAddr and manager data to output

This commit is contained in:
David Allen 2024-10-15 13:54:00 -06:00
parent e31e91ec15
commit deffdc48db
Signed by: towk
GPG key ID: 793B2924A49B3A3F

View file

@ -10,6 +10,7 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"strings"
"sync" "sync"
"time" "time"
@ -21,7 +22,9 @@ 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"
"github.com/stmcginnis/gofish/redfish"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@ -41,12 +44,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 +113,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 (
systems []crawler.InventoryDetail
managers []crawler.Manager
config = crawler.CrawlerConfig{
URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port), URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port),
Username: params.Username, Username: params.Username,
Password: params.Password, Password: params.Password,
Insecure: true, 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 +142,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)
@ -225,3 +249,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")
}