mirror of
https://github.com/davidallendj/magellan.git
synced 2025-12-20 11:37:01 -07:00
Merge branch 'main' into update-docker
This commit is contained in:
commit
90dff1f8e3
11 changed files with 494 additions and 144 deletions
15
README.md
15
README.md
|
|
@ -1,7 +1,8 @@
|
||||||
# Magellan
|
# Magellan
|
||||||
|
|
||||||
Magellan is a small tool designed to scan a network and collect BMC information
|
Magellan is a board management controller discovery tool designed to scan a network
|
||||||
to load the data into an [`hms-smd`](https://github.com/alexlovelltroy/hms-smd/tree/master) instance.
|
and collect information about a BMC node and load that data into an
|
||||||
|
[`hms-smd`](https://github.com/bikeshack/smd/tree/master) instance.
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
|
|
@ -13,9 +14,13 @@ Magellan is designed to do three things:
|
||||||
|
|
||||||
Magellan first tries to probe for specified hosts using the [`dora`](https://github.com/bmc-toolbox/dora)
|
Magellan first tries to probe for specified hosts using the [`dora`](https://github.com/bmc-toolbox/dora)
|
||||||
API. If that fails, it then tries to use its own built-in, simpler scanner as a fallback.
|
API. If that fails, it then tries to use its own built-in, simpler scanner as a fallback.
|
||||||
Next, it tries to query information about the BMC node using `bmclib` functions, but requires
|
This is done by sending a raw TCP request to a number of potential hosts over a
|
||||||
access to a redfish interface on the node to work. Once the BMC information is received,
|
network, and noting which requests are successful. At this point, `magellan` sees
|
||||||
it is then stored into `hms-smd` using its API.
|
no difference between a services.
|
||||||
|
|
||||||
|
Next, it tries to query information about the BMC node using `bmclib` functions,
|
||||||
|
but requires access to a redfish interface on the node to work. Once the BMC
|
||||||
|
information is received, it is then stored into `hms-smd` using its API.
|
||||||
|
|
||||||
In summary, `magellan` needs at minimum the following configured to work on each node:
|
In summary, `magellan` needs at minimum the following configured to work on each node:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ var collectCmd = &cobra.Command{
|
||||||
q := &magellan.QueryParams{
|
q := &magellan.QueryParams{
|
||||||
User: user,
|
User: user,
|
||||||
Pass: pass,
|
Pass: pass,
|
||||||
|
Protocol: protocol,
|
||||||
Drivers: drivers,
|
Drivers: drivers,
|
||||||
Preferred: preferredDriver,
|
Preferred: preferredDriver,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
|
|
@ -58,8 +59,9 @@ func init() {
|
||||||
collectCmd.PersistentFlags().IntVarP(&smd.Port, "port", "p", smd.Port, "set the port to the smd API")
|
collectCmd.PersistentFlags().IntVarP(&smd.Port, "port", "p", smd.Port, "set the port to the smd API")
|
||||||
collectCmd.PersistentFlags().StringVar(&user, "user", "", "set the BMC user")
|
collectCmd.PersistentFlags().StringVar(&user, "user", "", "set the BMC user")
|
||||||
collectCmd.PersistentFlags().StringVar(&pass, "pass", "", "set the BMC password")
|
collectCmd.PersistentFlags().StringVar(&pass, "pass", "", "set the BMC password")
|
||||||
|
collectCmd.PersistentFlags().StringVar(&protocol, "protocol", "https", "set the Redfish protocol")
|
||||||
collectCmd.PersistentFlags().StringVarP(&outputPath, "output", "o", "/tmp/magellan/data/", "set the path to store collection data")
|
collectCmd.PersistentFlags().StringVarP(&outputPath, "output", "o", "/tmp/magellan/data/", "set the path to store collection data")
|
||||||
collectCmd.PersistentFlags().BoolVar(&forceUpdate, "force-update", true, "set flag to force update data sent to SMD ")
|
collectCmd.PersistentFlags().BoolVar(&forceUpdate, "force-update", false, "set flag to force update data sent to SMD ")
|
||||||
collectCmd.PersistentFlags().StringVar(&preferredDriver, "preferred-driver", "ipmi", "set the preferred driver to use")
|
collectCmd.PersistentFlags().StringVar(&preferredDriver, "preferred-driver", "ipmi", "set the preferred driver to use")
|
||||||
collectCmd.PersistentFlags().StringVar(&ipmitoolPath, "ipmitool.path", "/usr/bin/ipmitool", "set the path for ipmitool")
|
collectCmd.PersistentFlags().StringVar(&ipmitoolPath, "ipmitool.path", "/usr/bin/ipmitool", "set the path for ipmitool")
|
||||||
collectCmd.PersistentFlags().BoolVar(&withSecureTLS, "secure-tls", false, "enable secure TLS")
|
collectCmd.PersistentFlags().BoolVar(&withSecureTLS, "secure-tls", false, "enable secure TLS")
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ var (
|
||||||
threads int
|
threads int
|
||||||
ports []int
|
ports []int
|
||||||
hosts []string
|
hosts []string
|
||||||
|
protocol string
|
||||||
withSecureTLS bool
|
withSecureTLS bool
|
||||||
certPoolFile string
|
certPoolFile string
|
||||||
user string
|
user string
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ var (
|
||||||
begin uint8
|
begin uint8
|
||||||
end uint8
|
end uint8
|
||||||
subnets []string
|
subnets []string
|
||||||
|
disableProbing bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var scanCmd = &cobra.Command{
|
var scanCmd = &cobra.Command{
|
||||||
|
|
@ -44,7 +45,7 @@ var scanCmd = &cobra.Command{
|
||||||
if threads <= 0 {
|
if threads <= 0 {
|
||||||
threads = mathutil.Clamp(len(hostsToScan), 1, 255)
|
threads = mathutil.Clamp(len(hostsToScan), 1, 255)
|
||||||
}
|
}
|
||||||
probeStates := magellan.ScanForAssets(hostsToScan, portsToScan, threads, timeout)
|
probeStates := magellan.ScanForAssets(hostsToScan, portsToScan, threads, timeout, disableProbing)
|
||||||
for _, r := range probeStates {
|
for _, r := range probeStates {
|
||||||
fmt.Printf("%s:%d (%s)\n", r.Host, r.Port, r.Protocol)
|
fmt.Printf("%s:%d (%s)\n", r.Host, r.Port, r.Protocol)
|
||||||
}
|
}
|
||||||
|
|
@ -60,11 +61,12 @@ var scanCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
scanCmd.PersistentFlags().StringSliceVar(&hosts, "host", []string{}, "set additional hosts to scan")
|
scanCmd.Flags().StringSliceVar(&hosts, "host", []string{}, "set additional hosts to scan")
|
||||||
scanCmd.PersistentFlags().IntSliceVar(&ports, "port", []int{}, "set the ports to scan")
|
scanCmd.Flags().IntSliceVar(&ports, "port", []int{}, "set the ports to scan")
|
||||||
scanCmd.Flags().Uint8Var(&begin, "begin", 0, "set the starting point for range of IP addresses")
|
scanCmd.Flags().Uint8Var(&begin, "begin", 0, "set the starting point for range of IP addresses")
|
||||||
scanCmd.Flags().Uint8Var(&end, "end", 255, "set the ending point for range of IP addresses")
|
scanCmd.Flags().Uint8Var(&end, "end", 255, "set the ending point for range of IP addresses")
|
||||||
scanCmd.Flags().StringSliceVar(&subnets, "subnet", []string{}, "set additional subnets")
|
scanCmd.Flags().StringSliceVar(&subnets, "subnet", []string{}, "set additional subnets")
|
||||||
|
scanCmd.Flags().BoolVar(&disableProbing, "disable-probing", false, "disable probing scanned results for BMC nodes")
|
||||||
|
|
||||||
rootCmd.AddCommand(scanCmd)
|
rootCmd.AddCommand(scanCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
82
cmd/update.go
Normal file
82
cmd/update.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
magellan "github.com/bikeshack/magellan/internal"
|
||||||
|
"github.com/bikeshack/magellan/internal/log"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
firmwarePath string
|
||||||
|
firmwareVersion string
|
||||||
|
component string
|
||||||
|
transferProtocol string
|
||||||
|
status bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var updateCmd = &cobra.Command{
|
||||||
|
Use: "update",
|
||||||
|
Short: "Update BMC node firmware",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
l := log.NewLogger(logrus.New(), logrus.DebugLevel)
|
||||||
|
q := &magellan.UpdateParams {
|
||||||
|
FirmwarePath: firmwarePath,
|
||||||
|
FirmwareVersion: firmwareVersion,
|
||||||
|
Component: component,
|
||||||
|
TransferProtocol: transferProtocol,
|
||||||
|
QueryParams: magellan.QueryParams{
|
||||||
|
Drivers: []string{"redfish"},
|
||||||
|
Preferred: "redfish",
|
||||||
|
Protocol: protocol,
|
||||||
|
Host: host,
|
||||||
|
User: user,
|
||||||
|
Pass: pass,
|
||||||
|
Timeout: timeout,
|
||||||
|
Port: port,
|
||||||
|
WithSecureTLS: withSecureTLS,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if required params are set
|
||||||
|
if host == "" || user == "" || pass == "" {
|
||||||
|
l.Log.Fatal("requires host, user, and pass to be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// get status if flag is set and exit
|
||||||
|
if status {
|
||||||
|
err := magellan.GetUpdateStatus(q)
|
||||||
|
if err != nil {
|
||||||
|
l.Log.Errorf("could not get update status: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// client, err := magellan.NewClient(l, &q.QueryParams)
|
||||||
|
// if err != nil {
|
||||||
|
// l.Log.Errorf("could not make client: %v", err)
|
||||||
|
// }
|
||||||
|
// err = magellan.UpdateFirmware(client, l, q)
|
||||||
|
err := magellan.UpdateFirmwareRemote(q)
|
||||||
|
if err != nil {
|
||||||
|
l.Log.Errorf("could not update firmware: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
updateCmd.Flags().StringVar(&host, "host", "", "set the BMC host")
|
||||||
|
updateCmd.Flags().IntVar(&port, "port", 443, "set the BMC port")
|
||||||
|
updateCmd.Flags().StringVar(&user, "user", "", "set the BMC user")
|
||||||
|
updateCmd.Flags().StringVar(&pass, "pass", "", "set the BMC password")
|
||||||
|
updateCmd.Flags().StringVar(&transferProtocol, "transfer-protocol", "HTTP", "set the transfer protocol")
|
||||||
|
updateCmd.Flags().StringVar(&protocol, "protocol", "https", "set the Redfish protocol")
|
||||||
|
updateCmd.Flags().StringVar(&firmwarePath, "firmware-path", "", "set the path to the firmware")
|
||||||
|
updateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "set the version of firmware to be installed")
|
||||||
|
updateCmd.Flags().StringVar(&component, "component", "", "set the component to upgrade")
|
||||||
|
updateCmd.Flags().BoolVar(&withSecureTLS, "secure-tls", false, "enable secure TLS")
|
||||||
|
updateCmd.Flags().BoolVar(&status, "status", false, "get the status of the update")
|
||||||
|
rootCmd.AddCommand(updateCmd)
|
||||||
|
}
|
||||||
|
|
@ -51,16 +51,11 @@ func AddRedfishEndpoint(data []byte, headers map[string]string) error {
|
||||||
// Add redfish endpoint via POST `/hsm/v2/Inventory/RedfishEndpoints` endpoint
|
// Add redfish endpoint via POST `/hsm/v2/Inventory/RedfishEndpoints` endpoint
|
||||||
url := makeEndpointUrl("/Inventory/RedfishEndpoints")
|
url := makeEndpointUrl("/Inventory/RedfishEndpoints")
|
||||||
res, body, err := util.MakeRequest(url, "POST", data, headers)
|
res, body, err := util.MakeRequest(url, "POST", data, headers)
|
||||||
if res == nil {
|
|
||||||
return fmt.Errorf("no response")
|
|
||||||
}
|
|
||||||
fmt.Printf("smd url: %v\n", url)
|
|
||||||
fmt.Printf("res: %v\n", res.Status)
|
|
||||||
fmt.Printf("body: %v\n", string(body))
|
|
||||||
if res != nil {
|
if res != nil {
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("could not add redfish endpoint")
|
return fmt.Errorf("could not add redfish endpoint")
|
||||||
}
|
}
|
||||||
|
fmt.Printf("%v (%v)\n%s\n", url, res.Status, string(body))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -72,12 +67,7 @@ func UpdateRedfishEndpoint(xname string, data []byte, headers map[string]string)
|
||||||
// Update redfish endpoint via PUT `/hsm/v2/Inventory/RedfishEndpoints` endpoint
|
// Update redfish endpoint via PUT `/hsm/v2/Inventory/RedfishEndpoints` endpoint
|
||||||
url := makeEndpointUrl("/Inventory/RedfishEndpoints/" + xname)
|
url := makeEndpointUrl("/Inventory/RedfishEndpoints/" + xname)
|
||||||
res, body, err := util.MakeRequest(url, "PUT", data, headers)
|
res, body, err := util.MakeRequest(url, "PUT", data, headers)
|
||||||
if res == nil {
|
fmt.Printf("%v (%v)\n%s\n", url, res.Status, string(body))
|
||||||
return fmt.Errorf("no response")
|
|
||||||
}
|
|
||||||
fmt.Printf("smd url: %v\n", url)
|
|
||||||
fmt.Printf("res: %v\n", res.Status)
|
|
||||||
fmt.Printf("body: %v\n", string(body))
|
|
||||||
if res != nil {
|
if res != nil {
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("could not update redfish endpoint")
|
return fmt.Errorf("could not update redfish endpoint")
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,12 @@ const (
|
||||||
HTTPS_PORT = 443
|
HTTPS_PORT = 443
|
||||||
)
|
)
|
||||||
|
|
||||||
type BMCProbeResult struct {
|
|
||||||
Host string `json:"host"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
State bool `json:"state"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: ...params were getting too long...
|
// NOTE: ...params were getting too long...
|
||||||
type QueryParams struct {
|
type QueryParams struct {
|
||||||
Host string
|
Host string
|
||||||
Port int
|
Port int
|
||||||
|
Protocol string
|
||||||
User string
|
User string
|
||||||
Pass string
|
Pass string
|
||||||
Drivers []string
|
Drivers []string
|
||||||
|
|
@ -59,9 +54,6 @@ type QueryParams struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) {
|
func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) {
|
||||||
// NOTE: bmclib.NewClient(host, port, user, pass)
|
|
||||||
// ...seems like the `port` params doesn't work like expected depending on interface
|
|
||||||
|
|
||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
|
|
@ -83,22 +75,20 @@ func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// only work if valid cert is provided
|
// only work if valid cert is provided
|
||||||
if q.WithSecureTLS {
|
if q.WithSecureTLS && q.CertPoolFile != "" {
|
||||||
var pool *x509.CertPool
|
pool := x509.NewCertPool()
|
||||||
if q.CertPoolFile != "" {
|
data, err := os.ReadFile(q.CertPoolFile)
|
||||||
pool = x509.NewCertPool()
|
if err != nil {
|
||||||
data, err := os.ReadFile(q.CertPoolFile)
|
return nil, fmt.Errorf("could not read cert pool file: %v", err)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not read cert pool file: %v", err)
|
|
||||||
}
|
|
||||||
pool.AppendCertsFromPEM(data)
|
|
||||||
}
|
}
|
||||||
|
pool.AppendCertsFromPEM(data)
|
||||||
// a nil pool uses the system certs
|
// a nil pool uses the system certs
|
||||||
clientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))
|
clientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))
|
||||||
}
|
}
|
||||||
url := ""
|
url := ""
|
||||||
|
fmt.Println(url)
|
||||||
if q.User != "" && q.Pass != "" {
|
if q.User != "" && q.Pass != "" {
|
||||||
url += fmt.Sprintf("https://%s:%s@%s", q.User, q.Pass, q.Host)
|
url += fmt.Sprintf("%s://%s:%s@%s", q.Protocol, q.User, q.Pass, q.Host)
|
||||||
} else {
|
} else {
|
||||||
url += q.Host
|
url += q.Host
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +102,7 @@ func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) {
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) error {
|
func CollectInfo(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) error {
|
||||||
// check for available probe states
|
// check for available probe states
|
||||||
if probeStates == nil {
|
if probeStates == nil {
|
||||||
return fmt.Errorf("no probe states found")
|
return fmt.Errorf("no probe states found")
|
||||||
|
|
@ -130,7 +120,7 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e
|
||||||
|
|
||||||
found := make([]string, 0, len(*probeStates))
|
found := make([]string, 0, len(*probeStates))
|
||||||
done := make(chan struct{}, q.Threads+1)
|
done := make(chan struct{}, q.Threads+1)
|
||||||
chanProbeState := make(chan BMCProbeResult, q.Threads+1)
|
chanProbeState := make(chan ScannedResult, q.Threads+1)
|
||||||
|
|
||||||
// generate custom xnames for bmcs
|
// generate custom xnames for bmcs
|
||||||
node := xnames.Node{
|
node := xnames.Node{
|
||||||
|
|
@ -163,22 +153,22 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e
|
||||||
node.NodeBMC += 1
|
node.NodeBMC += 1
|
||||||
|
|
||||||
// data to be sent to smd
|
// data to be sent to smd
|
||||||
data := make(map[string]any)
|
data := map[string]any{
|
||||||
data["ID"] = fmt.Sprintf("%v", node.String()[:len(node.String())-2])
|
"ID": fmt.Sprintf("%v", node.String()[:len(node.String())-2]),
|
||||||
data["Type"] = ""
|
"Type": "",
|
||||||
data["Name"] = ""
|
"Name": "",
|
||||||
data["FQDN"] = ps.Host
|
"FQDN": ps.Host,
|
||||||
data["User"] = q.User
|
"User": q.User,
|
||||||
data["Password"] = q.Pass
|
"Password": q.Pass,
|
||||||
data["IPAddr"] = ""
|
"MACRequired": true,
|
||||||
data["MACAddr"] = ""
|
"RediscoverOnUpdate": false,
|
||||||
data["RediscoverOnUpdate"] = false
|
}
|
||||||
|
|
||||||
// unmarshal json to send in correct format
|
// unmarshal json to send in correct format
|
||||||
var rm map[string]json.RawMessage
|
var rm map[string]json.RawMessage
|
||||||
|
|
||||||
// inventories
|
// inventories
|
||||||
inventory, err := QueryInventory(client, l, q)
|
inventory, err := QueryInventory(client, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Log.Errorf("could not query inventory (%v:%v): %v", q.Host, q.Port, err)
|
l.Log.Errorf("could not query inventory (%v:%v): %v", q.Host, q.Port, err)
|
||||||
}
|
}
|
||||||
|
|
@ -195,24 +185,13 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e
|
||||||
data["Chassis"] = rm["Chassis"]
|
data["Chassis"] = rm["Chassis"]
|
||||||
|
|
||||||
// ethernet interfaces
|
// ethernet interfaces
|
||||||
interfaces, err := QueryEthernetInterfaces(client, l, q)
|
// interfaces, err := QueryEthernetInterfaces(client, q)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
l.Log.Errorf("could not query ethernet interfaces: %v", err)
|
// l.Log.Errorf("could not query ethernet interfaces: %v", err)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
json.Unmarshal(interfaces, &rm)
|
// json.Unmarshal(interfaces, &rm)
|
||||||
data["Interface"] = rm["Interface"]
|
// data["Interfaces"] = rm["Interfaces"]
|
||||||
|
|
||||||
// get MAC address of first interface (for now...)
|
|
||||||
if len(rm["Interface"]) > 0 {
|
|
||||||
var i map[string]interface{}
|
|
||||||
json.Unmarshal(rm["Interface"], &i)
|
|
||||||
data["MACAddr"] = i["MACAddress"]
|
|
||||||
data["IPAddr"] = i["IPAddress"]
|
|
||||||
if i["FQDN"] != "" {
|
|
||||||
data["FQDN"] = rm["FQDN"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// storage
|
// storage
|
||||||
// storage, err := QueryStorage(q)
|
// storage, err := QueryStorage(q)
|
||||||
|
|
@ -224,16 +203,16 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e
|
||||||
// data["Storage"] = rm["Storage"]
|
// data["Storage"] = rm["Storage"]
|
||||||
|
|
||||||
// get specific processor info
|
// get specific processor info
|
||||||
procs, err := QueryProcessors(q)
|
// procs, err := QueryProcessors(q)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
l.Log.Errorf("could not query processors: %v", err)
|
// l.Log.Errorf("could not query processors: %v", err)
|
||||||
}
|
// }
|
||||||
var p map[string]interface{}
|
// var p map[string]interface{}
|
||||||
json.Unmarshal(procs, &p)
|
// json.Unmarshal(procs, &p)
|
||||||
data["Processors"] = rm["Processors"]
|
// data["Processors"] = rm["Processors"]
|
||||||
|
|
||||||
// systems
|
// systems
|
||||||
systems, err := QuerySystems(q)
|
systems, err := QuerySystems(client, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Log.Errorf("could not query systems: %v", err)
|
l.Log.Errorf("could not query systems: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -318,7 +297,7 @@ func CollectInfo(probeStates *[]BMCProbeResult, l *log.Logger, q *QueryParams) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueryMetadata(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) {
|
func QueryMetadata(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
// client, err := NewClient(l, q)
|
// client, err := NewClient(l, q)
|
||||||
|
|
||||||
// open BMC session and update driver registry
|
// open BMC session and update driver registry
|
||||||
|
|
@ -352,7 +331,7 @@ func QueryMetadata(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueryInventory(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) {
|
func QueryInventory(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
// open BMC session and update driver registry
|
// open BMC session and update driver registry
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
||||||
client.Registry.FilterForCompatible(ctx)
|
client.Registry.FilterForCompatible(ctx)
|
||||||
|
|
@ -385,7 +364,7 @@ func QueryInventory(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byt
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueryPowerState(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) {
|
func QueryPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
||||||
client.Registry.FilterForCompatible(ctx)
|
client.Registry.FilterForCompatible(ctx)
|
||||||
err := client.PreferProvider(q.Preferred).Open(ctx)
|
err := client.PreferProvider(q.Preferred).Open(ctx)
|
||||||
|
|
@ -417,7 +396,7 @@ func QueryPowerState(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]by
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueryUsers(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) {
|
func QueryUsers(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
// open BMC session and update driver registry
|
// open BMC session and update driver registry
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
||||||
client.Registry.FilterForCompatible(ctx)
|
client.Registry.FilterForCompatible(ctx)
|
||||||
|
|
@ -451,7 +430,7 @@ func QueryUsers(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, e
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueryBios(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) {
|
func QueryBios(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
// client, err := NewClient(l, q)
|
// client, err := NewClient(l, q)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return nil, fmt.Errorf("could not make query: %v", err)
|
// return nil, fmt.Errorf("could not make query: %v", err)
|
||||||
|
|
@ -463,26 +442,39 @@ func QueryBios(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, er
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueryEthernetInterfaces(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) {
|
func QueryEthernetInterfaces(client *bmclib.Client, q *QueryParams, systemID string) ([]byte, error) {
|
||||||
c, err := connectGofish(q)
|
c, err := connectGofish(q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not connect to bmc: %v", err)
|
return nil, fmt.Errorf("could not connect to bmc: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaces, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/")
|
systems, err := c.Service.Systems()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not query storage systems (%v:%v): %v", q.Host, q.Port, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var interfaces []*redfish.EthernetInterface
|
||||||
|
for _, system := range systems {
|
||||||
|
i, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/" + system.ID + "/EthernetInterfaces/")
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
interfaces = append(interfaces, i...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(interfaces) <= 0 {
|
||||||
return nil, fmt.Errorf("could not get ethernet interfaces: %v", err)
|
return nil, fmt.Errorf("could not get ethernet interfaces: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := map[string]any{"Interfaces": interfaces}
|
data := map[string]any{"EthernetInterfaces": interfaces}
|
||||||
b, err := json.MarshalIndent(data, "", " ")
|
b, err := json.MarshalIndent(data, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not marshal JSON: %v", err)
|
return nil, fmt.Errorf("could not marshal JSON: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if q.Verbose {
|
// if q.Verbose {
|
||||||
fmt.Printf("%v\n", string(b))
|
// fmt.Printf("%v\n", string(b))
|
||||||
}
|
// }
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -541,7 +533,7 @@ func QueryStorage(q *QueryParams) ([]byte, error) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func QuerySystems(q *QueryParams) ([]byte, error) {
|
func QuerySystems(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
c, err := connectGofish(q)
|
c, err := connectGofish(q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not connect to bmc (%v:%v): %v", q.Host, q.Port, err)
|
return nil, fmt.Errorf("could not connect to bmc (%v:%v): %v", q.Host, q.Port, err)
|
||||||
|
|
@ -549,10 +541,25 @@ func QuerySystems(q *QueryParams) ([]byte, error) {
|
||||||
|
|
||||||
systems, err := c.Service.Systems()
|
systems, err := c.Service.Systems()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not query storage systems (%v:%v): %v", q.Host, q.Port, err)
|
return nil, fmt.Errorf("could not query systems (%v:%v): %v", q.Host, q.Port, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data := map[string]any{"Systems": systems }
|
// query the system's ethernet interfaces
|
||||||
|
var temp []map[string]any
|
||||||
|
for _, system := range systems {
|
||||||
|
interfaces, err := QueryEthernetInterfaces(client, q, system.ID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var i map[string]any
|
||||||
|
json.Unmarshal(interfaces, &i)
|
||||||
|
temp = append(temp, map[string]any{
|
||||||
|
"Data": system,
|
||||||
|
"EthernetInterfaces": i["EthernetInterfaces"],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
data := map[string]any{"Systems": temp }
|
||||||
b, err := json.MarshalIndent(data, "", " ")
|
b, err := json.MarshalIndent(data, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not marshal JSON: %v", err)
|
return nil, fmt.Errorf("could not marshal JSON: %v", err)
|
||||||
|
|
@ -588,12 +595,7 @@ func QueryRegisteries(q *QueryParams) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func QueryProcessors(q *QueryParams) ([]byte, error) {
|
func QueryProcessors(q *QueryParams) ([]byte, error) {
|
||||||
baseUrl := "https://"
|
url := baseRedfishUrl(q) + "/Systems"
|
||||||
if q.User != "" && q.Pass != "" {
|
|
||||||
baseUrl += fmt.Sprintf("%s:%s@", q.User, q.Pass)
|
|
||||||
}
|
|
||||||
baseUrl += fmt.Sprintf("%s:%d", q.Host, q.Port)
|
|
||||||
url := baseUrl + "/redfish/v1/Systems"
|
|
||||||
res, body, err := util.MakeRequest(url, "GET", nil, nil)
|
res, body, err := util.MakeRequest(url, "GET", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("something went wrong: %v", err)
|
return nil, fmt.Errorf("something went wrong: %v", err)
|
||||||
|
|
@ -612,7 +614,7 @@ func QueryProcessors(q *QueryParams) ([]byte, error) {
|
||||||
// request data about each processor member on node
|
// request data about each processor member on node
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
var oid = member["@odata.id"].(string)
|
var oid = member["@odata.id"].(string)
|
||||||
var infoUrl = baseUrl + oid
|
var infoUrl = url + oid
|
||||||
res, _, err := util.MakeRequest(infoUrl, "GET", nil, nil)
|
res, _, err := util.MakeRequest(infoUrl, "GET", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("something went wrong: %v", err)
|
return nil, fmt.Errorf("something went wrong: %v", err)
|
||||||
|
|
@ -638,22 +640,23 @@ func QueryProcessors(q *QueryParams) ([]byte, error) {
|
||||||
func connectGofish(q *QueryParams) (*gofish.APIClient, error) {
|
func connectGofish(q *QueryParams) (*gofish.APIClient, error) {
|
||||||
config := makeGofishConfig(q)
|
config := makeGofishConfig(q)
|
||||||
c, err := gofish.Connect(config)
|
c, err := gofish.Connect(config)
|
||||||
c.Service.ProtocolFeaturesSupported = gofish.ProtocolFeaturesSupported{
|
if err != nil {
|
||||||
ExpandQuery: gofish.Expand{
|
|
||||||
ExpandAll: true,
|
return nil, fmt.Errorf("could not connect to redfish endpoint: %v", err)
|
||||||
Links: true,
|
}
|
||||||
},
|
if c != nil {
|
||||||
|
c.Service.ProtocolFeaturesSupported = gofish.ProtocolFeaturesSupported{
|
||||||
|
ExpandQuery: gofish.Expand{
|
||||||
|
ExpandAll: true,
|
||||||
|
Links: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeGofishConfig(q *QueryParams) gofish.ClientConfig {
|
func makeGofishConfig(q *QueryParams) gofish.ClientConfig {
|
||||||
url := "https://"
|
url := baseRedfishUrl(q)
|
||||||
if q.User != "" && q.Pass != "" {
|
|
||||||
url += fmt.Sprintf("%s:%s@", q.User, q.Pass)
|
|
||||||
}
|
|
||||||
url += fmt.Sprintf("%s:%d", q.Host, q.Port)
|
|
||||||
|
|
||||||
return gofish.ClientConfig{
|
return gofish.ClientConfig{
|
||||||
Endpoint: url,
|
Endpoint: url,
|
||||||
Username: q.User,
|
Username: q.User,
|
||||||
|
|
@ -691,3 +694,11 @@ func makeJson(object any) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return []byte(b), nil
|
return []byte(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func baseRedfishUrl(q *QueryParams) string {
|
||||||
|
url := fmt.Sprintf("%s://", q.Protocol)
|
||||||
|
if q.User != "" && q.Pass != "" {
|
||||||
|
url += fmt.Sprintf("%s:%s@", q.User, q.Pass)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s%s:%d", url, q.Host, q.Port)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,7 @@ import (
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InsertProbeResults(path string, states *[]magellan.BMCProbeResult) error {
|
func CreateProbeResultsIfNotExists(path string) (*sqlx.DB, error) {
|
||||||
if states == nil {
|
|
||||||
return fmt.Errorf("states == nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create database if it doesn't already exist
|
|
||||||
schema := `
|
schema := `
|
||||||
CREATE TABLE IF NOT EXISTS magellan_scanned_ports (
|
CREATE TABLE IF NOT EXISTS magellan_scanned_ports (
|
||||||
host TEXT NOT NULL,
|
host TEXT NOT NULL,
|
||||||
|
|
@ -25,9 +20,22 @@ func InsertProbeResults(path string, states *[]magellan.BMCProbeResult) error {
|
||||||
`
|
`
|
||||||
db, err := sqlx.Open("sqlite3", path)
|
db, err := sqlx.Open("sqlite3", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not open database: %v", err)
|
return nil, fmt.Errorf("could not open database: %v", err)
|
||||||
}
|
}
|
||||||
db.MustExec(schema)
|
db.MustExec(schema)
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InsertProbeResults(path string, states *[]magellan.ScannedResult) error {
|
||||||
|
if states == nil {
|
||||||
|
return fmt.Errorf("states == nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// create database if it doesn't already exist
|
||||||
|
db, err := CreateProbeResultsIfNotExists(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// insert all probe states into db
|
// insert all probe states into db
|
||||||
tx := db.MustBegin()
|
tx := db.MustBegin()
|
||||||
|
|
@ -46,13 +54,37 @@ func InsertProbeResults(path string, states *[]magellan.BMCProbeResult) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetProbeResults(path string) ([]magellan.BMCProbeResult, error) {
|
func DeleteProbeResults(path string, results *[]magellan.ScannedResult) error {
|
||||||
|
if results == nil {
|
||||||
|
return fmt.Errorf("no probe results found")
|
||||||
|
}
|
||||||
|
db, err := sqlx.Open("sqlite3", path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not open database: %v", err)
|
||||||
|
}
|
||||||
|
tx := db.MustBegin()
|
||||||
|
for _, state := range *results {
|
||||||
|
sql := `DELETE FROM magellan_scanned_ports WHERE host = :host, port = :port;`
|
||||||
|
_, err := tx.NamedExec(sql, &state)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("could not execute transaction: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not commit transaction: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProbeResults(path string) ([]magellan.ScannedResult, error) {
|
||||||
db, err := sqlx.Open("sqlite3", path)
|
db, err := sqlx.Open("sqlite3", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not open database: %v", err)
|
return nil, fmt.Errorf("could not open database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
results := []magellan.BMCProbeResult{}
|
results := []magellan.ScannedResult{}
|
||||||
err = db.Select(&results, "SELECT * FROM magellan_scanned_ports ORDER BY host ASC, port ASC;")
|
err = db.Select(&results, "SELECT * FROM magellan_scanned_ports ORDER BY host ASC, port ASC;")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not retrieve probes: %v", err)
|
return nil, fmt.Errorf("could not retrieve probes: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,24 @@ package magellan
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bikeshack/magellan/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func rawConnect(host string, ports []int, timeout int, keepOpenOnly bool) []BMCProbeResult {
|
type ScannedResult struct {
|
||||||
results := []BMCProbeResult{}
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
State bool `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawConnect(host string, ports []int, timeout int, keepOpenOnly bool) []ScannedResult {
|
||||||
|
results := []ScannedResult{}
|
||||||
for _, p := range ports {
|
for _, p := range ports {
|
||||||
result := BMCProbeResult{
|
result := ScannedResult{
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: p,
|
Port: p,
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
|
|
@ -50,8 +60,8 @@ func GenerateHosts(subnet string, begin uint8, end uint8) []string {
|
||||||
return hosts
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
func ScanForAssets(hosts []string, ports []int, threads int, timeout int) []BMCProbeResult {
|
func ScanForAssets(hosts []string, ports []int, threads int, timeout int, disableProbing bool) []ScannedResult {
|
||||||
results := make([]BMCProbeResult, 0, len(hosts))
|
results := make([]ScannedResult, 0, len(hosts))
|
||||||
done := make(chan struct{}, threads+1)
|
done := make(chan struct{}, threads+1)
|
||||||
chanHost := make(chan string, threads+1)
|
chanHost := make(chan string, threads+1)
|
||||||
// chanPort := make(chan int, threads+1)
|
// chanPort := make(chan int, threads+1)
|
||||||
|
|
@ -66,8 +76,25 @@ func ScanForAssets(hosts []string, ports []int, threads int, timeout int) []BMCP
|
||||||
wg.Done()
|
wg.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s := rawConnect(host, ports, timeout, true)
|
scannedResults := rawConnect(host, ports, timeout, true)
|
||||||
results = append(results, s...)
|
if !disableProbing {
|
||||||
|
probeResults := []ScannedResult{}
|
||||||
|
for _, result := range scannedResults {
|
||||||
|
url := fmt.Sprintf("https://%s:%d/redfish/v1/", result.Host, result.Port)
|
||||||
|
res, _, err := util.MakeRequest(url, "GET", nil, nil)
|
||||||
|
if err != nil || res == nil {
|
||||||
|
continue
|
||||||
|
} else if res.StatusCode != http.StatusOK {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
probeResults = append(probeResults, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results = append(results, probeResults...)
|
||||||
|
} else {
|
||||||
|
results = append(results, scannedResults...)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
195
internal/update.go
Normal file
195
internal/update.go
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
package magellan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bikeshack/magellan/internal/log"
|
||||||
|
"github.com/bikeshack/magellan/internal/util"
|
||||||
|
bmclib "github.com/bmc-toolbox/bmclib/v2"
|
||||||
|
"github.com/bmc-toolbox/bmclib/v2/constants"
|
||||||
|
bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type UpdateParams struct {
|
||||||
|
QueryParams
|
||||||
|
FirmwarePath string
|
||||||
|
FirmwareVersion string
|
||||||
|
Component string
|
||||||
|
TransferProtocol string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Does not work since OpenBMC, whic bmclib uses underneath, does not
|
||||||
|
// support multipart updates. See issue: https://github.com/bmc-toolbox/bmclib/issues/341
|
||||||
|
func UpdateFirmware(client *bmclib.Client, l *log.Logger, q *UpdateParams) error {
|
||||||
|
if q.Component == "" {
|
||||||
|
return fmt.Errorf("component is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// open BMC session and update driver registry
|
||||||
|
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
||||||
|
client.Registry.FilterForCompatible(ctx)
|
||||||
|
err := client.Open(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctxCancel()
|
||||||
|
return fmt.Errorf("could not connect to bmc: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer client.Close(ctx)
|
||||||
|
|
||||||
|
file, err := os.Open(q.FirmwarePath)
|
||||||
|
if err != nil {
|
||||||
|
ctxCancel()
|
||||||
|
return fmt.Errorf("could not open firmware path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
taskId, err := client.FirmwareInstall(ctx, q.Component, constants.FirmwareApplyOnReset, true, file)
|
||||||
|
if err != nil {
|
||||||
|
ctxCancel()
|
||||||
|
return fmt.Errorf("could not install firmware: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
ctxCancel()
|
||||||
|
return fmt.Errorf("context error: %v", ctx.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := client.FirmwareInstallStatus(ctx, q.FirmwareVersion, q.Component, taskId)
|
||||||
|
if err != nil {
|
||||||
|
// when its under update a connection refused is returned
|
||||||
|
if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "operation timed out") {
|
||||||
|
l.Log.Info("BMC refused connection, BMC most likely resetting...")
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, bmclibErrs.ErrSessionExpired) || strings.Contains(err.Error(), "session expired") {
|
||||||
|
err := client.Open(ctx)
|
||||||
|
if err != nil {
|
||||||
|
l.Log.Fatal(err, "bmc re-login failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("BMC session expired, logging in...")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case constants.FirmwareInstallRunning, constants.FirmwareInstallInitializing:
|
||||||
|
l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install running")
|
||||||
|
|
||||||
|
case constants.FirmwareInstallFailed:
|
||||||
|
ctxCancel()
|
||||||
|
l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install failed")
|
||||||
|
return fmt.Errorf("failed to install firmware")
|
||||||
|
|
||||||
|
case constants.FirmwareInstallComplete:
|
||||||
|
ctxCancel()
|
||||||
|
l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install completed")
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case constants.FirmwareInstallPowerCyleHost:
|
||||||
|
l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("host powercycle required")
|
||||||
|
|
||||||
|
if _, err := client.SetPowerState(ctx, "cycle"); err != nil {
|
||||||
|
ctxCancel()
|
||||||
|
l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("error power cycling host for install")
|
||||||
|
return fmt.Errorf("failed to install firmware")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxCancel()
|
||||||
|
l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("host power cycled, all done!")
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("unknown state returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateFirmwareRemote(q *UpdateParams) error {
|
||||||
|
url := baseRedfishUrl(&q.QueryParams) + "/redfish/v1/UpdateService/Actions/SimpleUpdate"
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("could not marshal data: %v", err)
|
||||||
|
}
|
||||||
|
res, body, err := util.MakeRequest(url, "POST", data, headers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("something went wrong: %v", err)
|
||||||
|
} else if res == nil {
|
||||||
|
return fmt.Errorf("no response returned (url: %s)", url)
|
||||||
|
}
|
||||||
|
if len(body) > 0 {
|
||||||
|
fmt.Printf("%d: %v\n", res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUpdateStatus(q *UpdateParams) error {
|
||||||
|
url := baseRedfishUrl(&q.QueryParams) + "/redfish/v1/UpdateService"
|
||||||
|
res, body, err := util.MakeRequest(url, "GET", nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("something went wrong: %v", err)
|
||||||
|
} else if res == nil {
|
||||||
|
return fmt.Errorf("no response returned (url: %s)", url)
|
||||||
|
} else if res.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("returned status code %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
if len(body) > 0 {
|
||||||
|
fmt.Printf("%d: %v\n", res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func UpdateFirmwareLocal(q *UpdateParams) error {
|
||||||
|
// fwUrl := baseUrl(&q.QueryParams) + ""
|
||||||
|
// url := baseUrl(&q.QueryParams) + "UpdateService/Actions/"
|
||||||
|
// headers := map[string]string {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // get etag from FW inventory
|
||||||
|
// response, err := util.MakeRequest()
|
||||||
|
|
||||||
|
// // load file from disk
|
||||||
|
// file, err := os.ReadFile(q.FirmwarePath)
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("could not read file: %v", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// switch q.TransferProtocol {
|
||||||
|
// case "HTTP":
|
||||||
|
// default:
|
||||||
|
// return fmt.Errorf("transfer protocol not supported")
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
@ -11,15 +11,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func PathExists(path string) (bool, error) {
|
func PathExists(path string) (bool, error) {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err == nil { return true, nil }
|
if err == nil { return true, nil }
|
||||||
if os.IsNotExist(err) { return false, nil }
|
if os.IsNotExist(err) { return false, nil }
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeRequest(url string, httpMethod string, body []byte, headers map[string]string) (*http.Response, []byte, error) {
|
func MakeRequest(url string, httpMethod string, body []byte, headers map[string]string) (*http.Response, []byte, error) {
|
||||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
req, _ := http.NewRequest(httpMethod, url, bytes.NewBuffer(body))
|
req, err := http.NewRequest(httpMethod, url, bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("could not create new HTTP request: %v", err)
|
||||||
|
}
|
||||||
req.Header.Add("User-Agent", "magellan")
|
req.Header.Add("User-Agent", "magellan")
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
req.Header.Add(k, v)
|
req.Header.Add(k, v)
|
||||||
|
|
@ -37,20 +40,20 @@ func MakeRequest(url string, httpMethod string, body []byte, headers map[string]
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeOutputDirectory(path string) (string, error) {
|
func MakeOutputDirectory(path string) (string, error) {
|
||||||
// get the current data + time using Go's stupid formatting
|
// get the current data + time using Go's stupid formatting
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
dirname := t.Format("2006-01-01 15:04:05")
|
dirname := t.Format("2006-01-01 15:04:05")
|
||||||
final := path + "/" + dirname
|
final := path + "/" + dirname
|
||||||
|
|
||||||
// check if path is valid and directory
|
// check if path is valid and directory
|
||||||
pathExists, err := PathExists(final);
|
pathExists, err := PathExists(final);
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return final, fmt.Errorf("could not check for existing path: %v", err)
|
return final, fmt.Errorf("could not check for existing path: %v", err)
|
||||||
}
|
}
|
||||||
if pathExists {
|
if pathExists {
|
||||||
// make sure it is directory with 0o644 permissions
|
// make sure it is directory with 0o644 permissions
|
||||||
return final, fmt.Errorf("found existing path: %v", final)
|
return final, fmt.Errorf("found existing path: %v", final)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create directory with data + time
|
// create directory with data + time
|
||||||
err = os.MkdirAll(final, 0766)
|
err = os.MkdirAll(final, 0766)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue