diff --git a/providers/redfish/bios.go b/internal/redfishwrapper/bios.go similarity index 68% rename from providers/redfish/bios.go rename to internal/redfishwrapper/bios.go index deb95ce5..a08be5a9 100644 --- a/providers/redfish/bios.go +++ b/internal/redfishwrapper/bios.go @@ -1,4 +1,4 @@ -package redfish +package redfishwrapper import ( "context" @@ -6,15 +6,15 @@ import ( bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" ) -func (c *Conn) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) { - systems, err := c.redfishwrapper.Systems() +func (c *Client) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) { + systems, err := c.Systems() if err != nil { return nil, err } biosConfig = make(map[string]string) for _, sys := range systems { - if !compatibleOdataID(sys.ODataID, systemsOdataIDs) { + if !c.compatibleOdataID(sys.ODataID, knownSystemsOdataIDs) { continue } diff --git a/internal/redfishwrapper/firmware.go b/internal/redfishwrapper/firmware.go index 32c83d64..3d8e3da2 100644 --- a/internal/redfishwrapper/firmware.go +++ b/internal/redfishwrapper/firmware.go @@ -106,9 +106,6 @@ func (c *Client) FirmwareUpload(ctx context.Context, updateFile *os.File, params return taskIDFromLocationHeader(location) } - fmt.Println(location) - fmt.Println(string(response)) - return taskIDFromResponseBody(response) } diff --git a/providers/redfish/inventory.go b/internal/redfishwrapper/inventory.go similarity index 64% rename from providers/redfish/inventory.go rename to internal/redfishwrapper/inventory.go index fec1b5fb..05046eb2 100644 --- a/providers/redfish/inventory.go +++ b/internal/redfishwrapper/inventory.go @@ -1,20 +1,19 @@ -package redfish +package redfishwrapper import ( "context" "strings" bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" - "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" "github.com/pkg/errors" "github.com/bmc-toolbox/common" - gofishrf "github.com/stmcginnis/gofish/redfish" + redfish "github.com/stmcginnis/gofish/redfish" ) var ( // Supported Chassis Odata IDs - chassisOdataIDs = []string{ + KnownChassisOdataIDs = []string{ // Dells "/redfish/v1/Chassis/Enclosure.Internal.0-1", "/redfish/v1/Chassis/System.Embedded.1", @@ -28,7 +27,7 @@ var ( } // Supported System Odata IDs - systemsOdataIDs = []string{ + knownSystemsOdataIDs = []string{ // Dells "/redfish/v1/Systems/System.Embedded.1", "/redfish/v1/Systems/System.Embedded.1/Bios", @@ -53,26 +52,29 @@ var ( } ) -// inventory struct wraps redfish connection -type inventory struct { - client *redfishwrapper.Client - failOnError bool - softwareInventory []*gofishrf.SoftwareInventory -} +// TODO: consider removing this +func (c *Client) compatibleOdataID(OdataID string, knownOdataIDs []string) bool { + for _, url := range knownOdataIDs { + if url == OdataID { + return true + } + } -func (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) { - // initialize inventory object - // the redfish client is assigned here to perform redfish Get/Delete requests - inv := &inventory{client: c.redfishwrapper, failOnError: c.failInventoryOnError} + return false +} - updateService, err := c.redfishwrapper.UpdateService() - if err != nil && inv.failOnError { +func (c *Client) Inventory(ctx context.Context, failOnError bool) (device *common.Device, err error) { + updateService, err := c.UpdateService() + if err != nil && failOnError { return nil, errors.Wrap(bmclibErrs.ErrRedfishSoftwareInventory, err.Error()) } + softwareInventory := []*redfish.SoftwareInventory{} + if updateService != nil { - inv.softwareInventory, err = updateService.FirmwareInventories() - if err != nil && inv.failOnError { + // nolint + softwareInventory, err = updateService.FirmwareInventories() + if err != nil && failOnError { return nil, errors.Wrap(bmclibErrs.ErrRedfishSoftwareInventory, err.Error()) } } @@ -82,20 +84,20 @@ func (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) device = &newDevice // populate device Chassis components attributes - err = inv.chassisAttributes(ctx, device) - if err != nil && inv.failOnError { + err = c.chassisAttributes(ctx, device, failOnError, softwareInventory) + if err != nil && failOnError { return nil, err } // populate device System components attributes - err = inv.systemAttributes(ctx, device) - if err != nil && inv.failOnError { + err = c.systemAttributes(ctx, device, failOnError, softwareInventory) + if err != nil && failOnError { return nil, err } // populate device BMC component attributes - err = inv.bmcAttributes(ctx, device) - if err != nil && inv.failOnError { + err = c.bmcAttributes(ctx, device, failOnError, softwareInventory) + if err != nil && failOnError { return nil, err } @@ -105,15 +107,15 @@ func (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) // DeviceVendorModel returns the device vendor and model attributes // bmcAttributes collects BMC component attributes -func (i *inventory) bmcAttributes(ctx context.Context, device *common.Device) (err error) { - managers, err := i.client.Managers(ctx) +func (c *Client) bmcAttributes(ctx context.Context, device *common.Device, failOnError bool, softwareInventory []*redfish.SoftwareInventory) (err error) { + managers, err := c.Managers(ctx) if err != nil { return err } var compatible int for _, manager := range managers { - if !compatibleOdataID(manager.ODataID, managerOdataIDs) { + if !c.compatibleOdataID(manager.ODataID, managerOdataIDs) { continue } @@ -141,7 +143,7 @@ func (i *inventory) bmcAttributes(ctx context.Context, device *common.Device) (e } // include additional firmware attributes from redfish firmware inventory - i.firmwareAttributes("", device.BMC.ID, device.BMC.Firmware) + c.firmwareAttributes("", device.BMC.ID, device.BMC.Firmware, softwareInventory) } if compatible == 0 { @@ -152,34 +154,34 @@ func (i *inventory) bmcAttributes(ctx context.Context, device *common.Device) (e } // chassisAttributes populates the device chassis attributes -func (i *inventory) chassisAttributes(ctx context.Context, device *common.Device) (err error) { - chassis, err := i.client.Chassis(ctx) +func (c *Client) chassisAttributes(ctx context.Context, device *common.Device, failOnError bool, softwareInventory []*redfish.SoftwareInventory) (err error) { + chassis, err := c.Chassis(ctx) if err != nil { return err } compatible := 0 for _, ch := range chassis { - if !compatibleOdataID(ch.ODataID, chassisOdataIDs) { + if !c.compatibleOdataID(ch.ODataID, KnownChassisOdataIDs) { continue } compatible++ - err = i.collectEnclosure(ch, device) - if err != nil && i.failOnError { + err = c.collectEnclosure(ch, device, softwareInventory) + if err != nil && failOnError { return err } - err = i.collectPSUs(ch, device) - if err != nil && i.failOnError { + err = c.collectPSUs(ch, device, softwareInventory) + if err != nil && failOnError { return err } } - err = i.collectCPLDs(device) - if err != nil && i.failOnError { + err = c.collectCPLDs(device, softwareInventory) + if err != nil && failOnError { return err } @@ -191,15 +193,15 @@ func (i *inventory) chassisAttributes(ctx context.Context, device *common.Device } -func (i *inventory) systemAttributes(ctx context.Context, device *common.Device) (err error) { - systems, err := i.client.Systems() +func (c *Client) systemAttributes(ctx context.Context, device *common.Device, failOnError bool, softwareInventory []*redfish.SoftwareInventory) (err error) { + systems, err := c.Systems() if err != nil { return err } compatible := 0 for _, sys := range systems { - if !compatibleOdataID(sys.ODataID, systemsOdataIDs) { + if !c.compatibleOdataID(sys.ODataID, knownSystemsOdataIDs) { continue } @@ -211,21 +213,27 @@ func (i *inventory) systemAttributes(ctx context.Context, device *common.Device) device.Serial = sys.SerialNumber } + type collectorFuncs []func( + sys *redfish.ComputerSystem, + device *common.Device, + softwareInventory []*redfish.SoftwareInventory, + ) error + // slice of collector methods - funcs := []func(sys *gofishrf.ComputerSystem, device *common.Device) error{ - i.collectCPUs, - i.collectDIMMs, - i.collectDrives, - i.collectBIOS, - i.collectNICs, - i.collectTPMs, - i.collectStorageControllers, + funcs := collectorFuncs{ + c.collectCPUs, + c.collectDIMMs, + c.collectDrives, + c.collectBIOS, + c.collectNICs, + c.collectTPMs, + c.collectStorageControllers, } // execute collector methods for _, f := range funcs { - err := f(sys, device) - if err != nil && i.failOnError { + err := f(sys, device, softwareInventory) + if err != nil && failOnError { return err } } @@ -245,8 +253,8 @@ func (i *inventory) systemAttributes(ctx context.Context, device *common.Device) // slug - the component slug constant // id - the component ID // previous - when true returns previously installed firmware, else returns the current -func (i *inventory) firmwareAttributes(slug, id string, firmwareObj *common.Firmware) { - if len(i.softwareInventory) == 0 { +func (c *Client) firmwareAttributes(slug, id string, firmwareObj *common.Firmware, softwareInventory []*redfish.SoftwareInventory) { + if len(softwareInventory) == 0 { return } @@ -254,7 +262,7 @@ func (i *inventory) firmwareAttributes(slug, id string, firmwareObj *common.Firm id = slug } - for _, inv := range i.softwareInventory { + for _, inv := range softwareInventory { // include previously installed firmware attributes if strings.HasPrefix(inv.ID, "Previous") { if strings.Contains(inv.ID, id) || strings.EqualFold(slug, inv.Name) { @@ -292,13 +300,3 @@ func (i *inventory) firmwareAttributes(slug, id string, firmwareObj *common.Firm } } } - -func compatibleOdataID(OdataID string, knownOdataIDs []string) bool { - for _, url := range knownOdataIDs { - if url == OdataID { - return true - } - } - - return false -} diff --git a/providers/redfish/inventory_collect.go b/internal/redfishwrapper/inventory_collect.go similarity index 78% rename from providers/redfish/inventory_collect.go rename to internal/redfishwrapper/inventory_collect.go index aad65d3b..e441638d 100644 --- a/providers/redfish/inventory_collect.go +++ b/internal/redfishwrapper/inventory_collect.go @@ -1,17 +1,17 @@ -package redfish +package redfishwrapper import ( "math" "strings" "github.com/bmc-toolbox/common" - gofishrf "github.com/stmcginnis/gofish/redfish" + "github.com/stmcginnis/gofish/redfish" ) // defines various inventory collection helper methods // collectEnclosure collects Enclosure information -func (i *inventory) collectEnclosure(ch *gofishrf.Chassis, device *common.Device) (err error) { +func (c *Client) collectEnclosure(ch *redfish.Chassis, device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { e := &common.Enclosure{ Common: common.Common{ Description: ch.Description, @@ -34,7 +34,7 @@ func (i *inventory) collectEnclosure(ch *gofishrf.Chassis, device *common.Device } // include additional firmware attributes from redfish firmware inventory - i.firmwareAttributes(common.SlugEnclosure, e.ID, e.Firmware) + c.firmwareAttributes(common.SlugEnclosure, e.ID, e.Firmware, softwareInventory) device.Enclosures = append(device.Enclosures, e) @@ -42,7 +42,7 @@ func (i *inventory) collectEnclosure(ch *gofishrf.Chassis, device *common.Device } // collectPSUs collects Power Supply Unit component information -func (i *inventory) collectPSUs(ch *gofishrf.Chassis, device *common.Device) (err error) { +func (c *Client) collectPSUs(ch *redfish.Chassis, device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { power, err := ch.Power() if err != nil { return err @@ -74,7 +74,7 @@ func (i *inventory) collectPSUs(ch *gofishrf.Chassis, device *common.Device) (er } // include additional firmware attributes from redfish firmware inventory - i.firmwareAttributes(common.SlugPSU, psu.ID, p.Firmware) + c.firmwareAttributes(common.SlugPSU, psu.ID, p.Firmware, softwareInventory) device.PSUs = append(device.PSUs, p) @@ -83,7 +83,7 @@ func (i *inventory) collectPSUs(ch *gofishrf.Chassis, device *common.Device) (er } // collectTPMs collects Trusted Platform Module component information -func (i *inventory) collectTPMs(sys *gofishrf.ComputerSystem, device *common.Device) (err error) { +func (c *Client) collectTPMs(sys *redfish.ComputerSystem, device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { for _, module := range sys.TrustedModules { tpm := &common.TPM{ Common: common.Common{ @@ -100,7 +100,7 @@ func (i *inventory) collectTPMs(sys *gofishrf.ComputerSystem, device *common.Dev } // include additional firmware attributes from redfish firmware inventory - i.firmwareAttributes(common.SlugTPM, "TPM", tpm.Firmware) + c.firmwareAttributes(common.SlugTPM, "TPM", tpm.Firmware, softwareInventory) device.TPMs = append(device.TPMs, tpm) } @@ -109,7 +109,7 @@ func (i *inventory) collectTPMs(sys *gofishrf.ComputerSystem, device *common.Dev } // collectNICs collects network interface component information -func (i *inventory) collectNICs(sys *gofishrf.ComputerSystem, device *common.Device) (err error) { +func (c *Client) collectNICs(sys *redfish.ComputerSystem, device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { if sys == nil || device == nil { return nil } @@ -163,17 +163,17 @@ func (i *inventory) collectNICs(sys *gofishrf.ComputerSystem, device *common.Dev // populate network ports general data nicPort := &common.NICPort{} - i.collectNetworkPortInfo(nicPort, adapter, networkPort, portFirmwareVersion) + c.collectNetworkPortInfo(nicPort, adapter, networkPort, portFirmwareVersion, softwareInventory) - if networkPort.ActiveLinkTechnology == gofishrf.EthernetLinkNetworkTechnology { + if networkPort.ActiveLinkTechnology == redfish.EthernetLinkNetworkTechnology { // ethernet specific data - i.collectEthernetInfo(nicPort, ethernetInterfaces) + c.collectEthernetInfo(nicPort, ethernetInterfaces) } n.NICPorts = append(n.NICPorts, nicPort) } // include additional firmware attributes from redfish firmware inventory - i.firmwareAttributes(common.SlugNIC, n.ID, n.Firmware) + c.firmwareAttributes(common.SlugNIC, n.ID, n.Firmware, softwareInventory) if len(portFirmwareVersion) > 0 { if n.Firmware == nil { n.Firmware = &common.Firmware{} @@ -187,8 +187,13 @@ func (i *inventory) collectNICs(sys *gofishrf.ComputerSystem, device *common.Dev return nil } -func (i *inventory) collectNetworkPortInfo( - nicPort *common.NICPort, adapter *gofishrf.NetworkAdapter, networkPort *gofishrf.NetworkPort, firmware string) { +func (c *Client) collectNetworkPortInfo( + nicPort *common.NICPort, + adapter *redfish.NetworkAdapter, + networkPort *redfish.NetworkPort, + firmware string, + softwareInventory []*redfish.SoftwareInventory, +) { if adapter != nil { nicPort.Vendor = adapter.Manufacturer @@ -221,7 +226,7 @@ func (i *inventory) collectNetworkPortInfo( } } - i.firmwareAttributes(common.SlugNIC, networkPort.ID, nicPort.Firmware) + c.firmwareAttributes(common.SlugNIC, networkPort.ID, nicPort.Firmware, softwareInventory) } if len(firmware) > 0 { if nicPort.Firmware == nil { @@ -231,7 +236,7 @@ func (i *inventory) collectNetworkPortInfo( } } -func (i *inventory) collectEthernetInfo(nicPort *common.NICPort, ethernetInterfaces []*gofishrf.EthernetInterface) { +func (c *Client) collectEthernetInfo(nicPort *common.NICPort, ethernetInterfaces []*redfish.EthernetInterface) { if nicPort == nil { return } @@ -273,7 +278,7 @@ func (i *inventory) collectEthernetInfo(nicPort *common.NICPort, ethernetInterfa } } -func getFirmwareVersionFromController(controllers []gofishrf.Controllers, portCount int) string { +func getFirmwareVersionFromController(controllers []redfish.Controllers, portCount int) string { for _, controller := range controllers { if controller.ControllerCapabilities.NetworkPortCount == portCount { return controller.FirmwarePackageVersion @@ -282,7 +287,7 @@ func getFirmwareVersionFromController(controllers []gofishrf.Controllers, portCo return "" } -func (i *inventory) collectBIOS(sys *gofishrf.ComputerSystem, device *common.Device) (err error) { +func (c *Client) collectBIOS(sys *redfish.ComputerSystem, device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { device.BIOS = &common.BIOS{ Common: common.Common{ Firmware: &common.Firmware{ @@ -301,13 +306,13 @@ func (i *inventory) collectBIOS(sys *gofishrf.ComputerSystem, device *common.Dev } // include additional firmware attributes from redfish firmware inventory - i.firmwareAttributes(common.SlugBIOS, "BIOS", device.BIOS.Firmware) + c.firmwareAttributes(common.SlugBIOS, "BIOS", device.BIOS.Firmware, softwareInventory) return nil } // collectDrives collects drive component information -func (i *inventory) collectDrives(sys *gofishrf.ComputerSystem, device *common.Device) (err error) { +func (c *Client) collectDrives(sys *redfish.ComputerSystem, device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { storage, err := sys.Storage() if err != nil { return err @@ -351,7 +356,7 @@ func (i *inventory) collectDrives(sys *gofishrf.ComputerSystem, device *common.D } // include additional firmware attributes from redfish firmware inventory - i.firmwareAttributes("Disk", drive.ID, d.Firmware) + c.firmwareAttributes("Disk", drive.ID, d.Firmware, softwareInventory) device.Drives = append(device.Drives, d) @@ -363,7 +368,7 @@ func (i *inventory) collectDrives(sys *gofishrf.ComputerSystem, device *common.D } // collectStorageControllers populates the device with Storage controller component attributes -func (i *inventory) collectStorageControllers(sys *gofishrf.ComputerSystem, device *common.Device) (err error) { +func (c *Client) collectStorageControllers(sys *redfish.ComputerSystem, device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { storage, err := sys.Storage() if err != nil { return err @@ -372,7 +377,7 @@ func (i *inventory) collectStorageControllers(sys *gofishrf.ComputerSystem, devi for _, member := range storage { for _, controller := range member.StorageControllers { - c := &common.StorageController{ + cs := &common.StorageController{ Common: common.Common{ Description: controller.Name, Vendor: common.FormatVendorName(controller.Manufacturer), @@ -392,23 +397,22 @@ func (i *inventory) collectStorageControllers(sys *gofishrf.ComputerSystem, devi } // In some cases the storage controller model number is present in the Name field - if strings.TrimSpace(c.Model) == "" && strings.TrimSpace(controller.Name) != "" { - c.Model = controller.Name + if strings.TrimSpace(cs.Model) == "" && strings.TrimSpace(controller.Name) != "" { + cs.Model = controller.Name } // include additional firmware attributes from redfish firmware inventory - i.firmwareAttributes(c.Description, c.ID, c.Firmware) + c.firmwareAttributes(cs.Description, cs.ID, cs.Firmware, softwareInventory) - device.StorageControllers = append(device.StorageControllers, c) + device.StorageControllers = append(device.StorageControllers, cs) } - } return nil } // collectCPUs populates the device with CPU component attributes -func (i *inventory) collectCPUs(sys *gofishrf.ComputerSystem, device *common.Device) (err error) { +func (c *Client) collectCPUs(sys *redfish.ComputerSystem, device *common.Device, _ []*redfish.SoftwareInventory) (err error) { procs, err := sys.Processors() if err != nil { return err @@ -447,7 +451,7 @@ func (i *inventory) collectCPUs(sys *gofishrf.ComputerSystem, device *common.Dev } // collectDIMMs populates the device with memory component attributes -func (i *inventory) collectDIMMs(sys *gofishrf.ComputerSystem, device *common.Device) (err error) { +func (c *Client) collectDIMMs(sys *redfish.ComputerSystem, device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { dimms, err := sys.Memory() if err != nil { return err @@ -479,7 +483,7 @@ func (i *inventory) collectDIMMs(sys *gofishrf.ComputerSystem, device *common.De } // collecCPLDs populates the device with CPLD component attributes -func (i *inventory) collectCPLDs(device *common.Device) (err error) { +func (c *Client) collectCPLDs(device *common.Device, softwareInventory []*redfish.SoftwareInventory) (err error) { cpld := &common.CPLD{ Common: common.Common{ @@ -489,7 +493,7 @@ func (i *inventory) collectCPLDs(device *common.Device) (err error) { }, } - i.firmwareAttributes(common.SlugCPLD, "", cpld.Firmware) + c.firmwareAttributes(common.SlugCPLD, "", cpld.Firmware, softwareInventory) name, exists := cpld.Firmware.Metadata["name"] if exists { cpld.Description = name diff --git a/providers/redfish/inventory_collect_test.go b/internal/redfishwrapper/inventory_collect_test.go similarity index 86% rename from providers/redfish/inventory_collect_test.go rename to internal/redfishwrapper/inventory_collect_test.go index 766cf312..35f95d5b 100644 --- a/providers/redfish/inventory_collect_test.go +++ b/internal/redfishwrapper/inventory_collect_test.go @@ -1,20 +1,20 @@ -package redfish +package redfishwrapper import ( - "github.com/bmc-toolbox/common" - common2 "github.com/stmcginnis/gofish/common" - gofishrf "github.com/stmcginnis/gofish/redfish" "reflect" "testing" -) -func Test_inventory_collectNetworkPortInfo(t *testing.T) { + "github.com/bmc-toolbox/common" + common2 "github.com/stmcginnis/gofish/common" + redfish "github.com/stmcginnis/gofish/redfish" +) - testAdapter := &gofishrf.NetworkAdapter{ +func TestInventoryCollectNetworkPortInfo(t *testing.T) { + testAdapter := &redfish.NetworkAdapter{ Manufacturer: "Acme", Model: "Anvil 3000", } - testNetworkPort := &gofishrf.NetworkPort{ + testNetworkPort := &redfish.NetworkPort{ Entity: common2.Entity{ID: "NetworkPort-1"}, Description: "NetworkPort One", VendorID: "Vendor-ID", @@ -67,8 +67,8 @@ func Test_inventory_collectNetworkPortInfo(t *testing.T) { tests := []struct { name string nicPort *common.NICPort - adapter *gofishrf.NetworkAdapter - networkPort *gofishrf.NetworkPort + adapter *redfish.NetworkAdapter + networkPort *redfish.NetworkPort firmware string wantedNicPort *common.NICPort }{ @@ -103,8 +103,8 @@ func Test_inventory_collectNetworkPortInfo(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - i := &inventory{} - i.collectNetworkPortInfo(tt.nicPort, tt.adapter, tt.networkPort, tt.firmware) + c := Client{} + c.collectNetworkPortInfo(tt.nicPort, tt.adapter, tt.networkPort, tt.firmware, []*redfish.SoftwareInventory{}) if !reflect.DeepEqual(tt.nicPort, tt.wantedNicPort) { t.Errorf("collectNetworkPortInfo() gotNicPort = %v, want %v", tt.nicPort, tt.wantedNicPort) } @@ -113,17 +113,17 @@ func Test_inventory_collectNetworkPortInfo(t *testing.T) { } -func Test_inventory_collectEthernetInfo(t *testing.T) { +func TestInventoryCollectEthernetInfo(t *testing.T) { testNicPortID := "test NIC port ID" testEthernetID := "test NIC port ID ethernet" testNicPort := &common.NICPort{ ID: testNicPortID, } - testUnmatchingEthList := []*gofishrf.EthernetInterface{ + testUnmatchingEthList := []*redfish.EthernetInterface{ {Entity: common2.Entity{ID: "other ID"}}, {Entity: common2.Entity{ID: "another one"}}, } - testMatchingEth := &gofishrf.EthernetInterface{ + testMatchingEth := &redfish.EthernetInterface{ Entity: common2.Entity{ID: testEthernetID}, Description: "Ethernet Interface", Status: common2.Status{ @@ -155,12 +155,12 @@ func Test_inventory_collectEthernetInfo(t *testing.T) { tests := []struct { name string nicPort *common.NICPort - ethernetInterfaces []*gofishrf.EthernetInterface + ethernetInterfaces []*redfish.EthernetInterface wantedNicPort *common.NICPort }{ {name: "nil"}, {name: "empty", nicPort: testNicPort, wantedNicPort: testNicPort}, - {name: "empty ethernet list", nicPort: testNicPort, ethernetInterfaces: []*gofishrf.EthernetInterface{}, wantedNicPort: testNicPort}, + {name: "empty ethernet list", nicPort: testNicPort, ethernetInterfaces: []*redfish.EthernetInterface{}, wantedNicPort: testNicPort}, {name: "unmatching ethernet list", nicPort: testNicPort, ethernetInterfaces: testUnmatchingEthList, wantedNicPort: testNicPort}, { name: "full", @@ -170,8 +170,8 @@ func Test_inventory_collectEthernetInfo(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - i := &inventory{} - i.collectEthernetInfo(tt.nicPort, tt.ethernetInterfaces) + c := Client{} + c.collectEthernetInfo(tt.nicPort, tt.ethernetInterfaces) }) } } diff --git a/internal/redfishwrapper/power.go b/internal/redfishwrapper/power.go index f2df6b5a..53d412c8 100644 --- a/internal/redfishwrapper/power.go +++ b/internal/redfishwrapper/power.go @@ -11,6 +11,25 @@ import ( rf "github.com/stmcginnis/gofish/redfish" ) +// PowerSet sets the power state of a server +func (c *Client) PowerSet(ctx context.Context, state string) (ok bool, err error) { + // TODO: create consts for the state values + switch strings.ToLower(state) { + case "on": + return c.SystemPowerOn(ctx) + case "off": + return c.SystemForceOff(ctx) + case "soft": + return c.SystemPowerOff(ctx) + case "reset": + return c.SystemReset(ctx) + case "cycle": + return c.SystemPowerCycle(ctx) + default: + return false, errors.New("unknown power action") + } +} + // BMCReset powercycles the BMC. func (c *Client) BMCReset(ctx context.Context, resetType string) (ok bool, err error) { if err := c.SessionActive(); err != nil { diff --git a/internal/redfishwrapper/system.go b/internal/redfishwrapper/system.go index 5d2683a7..784e5681 100644 --- a/internal/redfishwrapper/system.go +++ b/internal/redfishwrapper/system.go @@ -6,13 +6,13 @@ import ( bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" "github.com/pkg/errors" - gofishrf "github.com/stmcginnis/gofish/redfish" + redfish "github.com/stmcginnis/gofish/redfish" ) // The methods here should be a thin wrapper so as to only guard the client from authentication failures. // AccountService gets the Redfish AccountService.d -func (c *Client) AccountService() (*gofishrf.AccountService, error) { +func (c *Client) AccountService() (*redfish.AccountService, error) { if err := c.SessionActive(); err != nil { return nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error()) } @@ -21,7 +21,7 @@ func (c *Client) AccountService() (*gofishrf.AccountService, error) { } // UpdateService gets the update service instance. -func (c *Client) UpdateService() (*gofishrf.UpdateService, error) { +func (c *Client) UpdateService() (*redfish.UpdateService, error) { if err := c.SessionActive(); err != nil { return nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error()) } @@ -30,7 +30,7 @@ func (c *Client) UpdateService() (*gofishrf.UpdateService, error) { } // Systems get the system instances from the service. -func (c *Client) Systems() ([]*gofishrf.ComputerSystem, error) { +func (c *Client) Systems() ([]*redfish.ComputerSystem, error) { if err := c.SessionActive(); err != nil { return nil, err } @@ -39,7 +39,7 @@ func (c *Client) Systems() ([]*gofishrf.ComputerSystem, error) { } // Managers gets the manager instances of this service. -func (c *Client) Managers(ctx context.Context) ([]*gofishrf.Manager, error) { +func (c *Client) Managers(ctx context.Context) ([]*redfish.Manager, error) { if err := c.SessionActive(); err != nil { return nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error()) } @@ -48,7 +48,7 @@ func (c *Client) Managers(ctx context.Context) ([]*gofishrf.Manager, error) { } // Chassis gets the chassis instances managed by this service. -func (c *Client) Chassis(ctx context.Context) ([]*gofishrf.Chassis, error) { +func (c *Client) Chassis(ctx context.Context) ([]*redfish.Chassis, error) { if err := c.SessionActive(); err != nil { return nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error()) } diff --git a/internal/redfishwrapper/task.go b/internal/redfishwrapper/task.go index 0d5227be..5932b0d1 100644 --- a/internal/redfishwrapper/task.go +++ b/internal/redfishwrapper/task.go @@ -8,14 +8,15 @@ import ( "github.com/bmc-toolbox/bmclib/v2/constants" bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" "github.com/pkg/errors" - gofishrf "github.com/stmcginnis/gofish/redfish" + "github.com/stmcginnis/gofish/common" + redfish "github.com/stmcginnis/gofish/redfish" ) var ( errUnexpectedTaskState = errors.New("unexpected task state") ) -func (c *Client) Task(ctx context.Context, taskID string) (*gofishrf.Task, error) { +func (c *Client) Task(ctx context.Context, taskID string) (*redfish.Task, error) { tasks, err := c.Tasks(ctx) if err != nil { return nil, errors.Wrap(err, "error querying redfish tasks") @@ -38,12 +39,39 @@ func (c *Client) TaskStatus(ctx context.Context, taskID string) (constants.TaskS return "", "", errors.Wrap(err, "error querying redfish for taskID: "+taskID) } - taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", task.ID, task.TaskState, task.TaskStatus) + taskInfo := fmt.Sprintf( + "id: %s, state: %s, status: %s", + task.ID, + task.TaskState, + task.TaskStatus, + ) + + // task message include information that help debug a cause of failure + if msgs := c.taskMessagesAsString(task.Messages); msgs != "" { + taskInfo += ", messages: " + msgs + } s := c.ConvertTaskState(string(task.TaskState)) return s, taskInfo, nil } +func (c *Client) taskMessagesAsString(messages []common.Message) string { + if len(messages) == 0 { + return "" + } + + var found []string + for _, m := range messages { + if m.Message == "" { + continue + } + + found = append(found, m.Message) + } + + return strings.Join(found, ",") +} + func (c *Client) ConvertTaskState(state string) constants.TaskState { switch strings.ToLower(state) { case "starting", "downloading", "downloaded": diff --git a/internal/redfishwrapper/task_test.go b/internal/redfishwrapper/task_test.go index cd7fbabd..43aa5d26 100644 --- a/internal/redfishwrapper/task_test.go +++ b/internal/redfishwrapper/task_test.go @@ -2,7 +2,6 @@ package redfishwrapper import ( "context" - "fmt" "net/http" "net/http/httptest" "net/url" @@ -288,7 +287,6 @@ func TestTask(t *testing.T) { got, err := client.Task(ctx, tc.taskID) if tc.err != nil { - fmt.Println(err) assert.ErrorContains(t, err, tc.err.Error()) return } diff --git a/providers/dell/idrac.go b/providers/dell/idrac.go index b0be39d1..ab8f166e 100644 --- a/providers/dell/idrac.go +++ b/providers/dell/idrac.go @@ -37,9 +37,11 @@ var ( Features = registrar.Features{ providers.FeatureScreenshot, providers.FeaturePowerState, + providers.FeaturePowerSet, providers.FeatureFirmwareInstallSteps, providers.FeatureFirmwareUploadInitiateInstall, providers.FeatureFirmwareTaskStatus, + providers.FeatureInventoryRead, } ) @@ -203,6 +205,16 @@ func (c *Conn) PowerStateGet(ctx context.Context) (state string, err error) { return c.redfishwrapper.SystemPowerStatus(ctx) } +// PowerSet sets the power state of a server +func (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error) { + return c.redfishwrapper.PowerSet(ctx, state) +} + +// Inventory collects hardware inventory and install firmware information +func (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) { + return c.redfishwrapper.Inventory(ctx, false) +} + var errManufacturerUnknown = errors.New("error identifying device manufacturer") // deviceManufacturer returns the device manufacturer and model attributes diff --git a/providers/redfish/redfish.go b/providers/redfish/redfish.go index 09e8aa64..876eb496 100644 --- a/providers/redfish/redfish.go +++ b/providers/redfish/redfish.go @@ -4,14 +4,13 @@ import ( "context" "crypto/x509" "net/http" - "strings" "github.com/bmc-toolbox/bmclib/v2/internal/httpclient" "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" "github.com/bmc-toolbox/bmclib/v2/providers" + "github.com/bmc-toolbox/common" "github.com/go-logr/logr" "github.com/jacobweinstock/registrar" - "github.com/pkg/errors" bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" ) @@ -192,20 +191,7 @@ func (c *Conn) PowerStateGet(ctx context.Context) (state string, err error) { // PowerSet sets the power state of a server func (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error) { - switch strings.ToLower(state) { - case "on": - return c.redfishwrapper.SystemPowerOn(ctx) - case "off": - return c.redfishwrapper.SystemForceOff(ctx) - case "soft": - return c.redfishwrapper.SystemPowerOff(ctx) - case "reset": - return c.redfishwrapper.SystemReset(ctx) - case "cycle": - return c.redfishwrapper.SystemPowerCycle(ctx) - default: - return false, errors.New("unknown power action") - } + return c.redfishwrapper.PowerSet(ctx, state) } // BootDeviceSet sets the boot device @@ -217,3 +203,13 @@ func (c *Conn) BootDeviceSet(ctx context.Context, bootDevice string, setPersiste func (c *Conn) SetVirtualMedia(ctx context.Context, kind string, mediaURL string) (ok bool, err error) { return c.redfishwrapper.SetVirtualMedia(ctx, kind, mediaURL) } + +// Inventory collects hardware inventory and install firmware information +func (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) { + return c.redfishwrapper.Inventory(ctx, c.failInventoryOnError) +} + +// GetBiosConfiguration return bios configuration +func (c *Conn) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) { + return c.redfishwrapper.GetBiosConfiguration(ctx) +} diff --git a/providers/supermicro/floppy.go b/providers/supermicro/floppy.go index 3c652890..835b70f4 100644 --- a/providers/supermicro/floppy.go +++ b/providers/supermicro/floppy.go @@ -20,7 +20,7 @@ var ( ) func (c *Client) floppyImageMounted(ctx context.Context) (bool, error) { - if err := c.openRedfish(ctx); err != nil { + if err := c.serviceClient.redfishSession(ctx); err != nil { return false, err } diff --git a/providers/supermicro/supermicro.go b/providers/supermicro/supermicro.go index 47f167b9..6824fd73 100644 --- a/providers/supermicro/supermicro.go +++ b/providers/supermicro/supermicro.go @@ -20,6 +20,7 @@ import ( "github.com/bmc-toolbox/bmclib/v2/internal/httpclient" "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" "github.com/bmc-toolbox/bmclib/v2/providers" + "github.com/bmc-toolbox/common" "github.com/go-logr/logr" "github.com/jacobweinstock/registrar" @@ -46,6 +47,9 @@ var ( providers.FeatureFirmwareInstallUploaded, providers.FeatureFirmwareTaskStatus, providers.FeatureFirmwareInstallSteps, + providers.FeatureInventoryRead, + providers.FeaturePowerSet, + providers.FeaturePowerState, } ) @@ -110,6 +114,8 @@ type bmcQueryor interface { // returns the device model, that was queried previously with queryDeviceModel deviceModel() (model string) supportsInstall(component string) error + inventory(ctx context.Context) (*common.Device, error) + powerSet(ctx context.Context, state string) (ok bool, err error) } // New returns connection with a Supermicro client initialized @@ -184,6 +190,25 @@ func (c *Client) Open(ctx context.Context) (err error) { return nil } +// PowerStateGet gets the power state of a BMC machine +func (c *Client) PowerStateGet(ctx context.Context) (state string, err error) { + if c.serviceClient == nil || c.serviceClient.redfish == nil { + return "", errors.Wrap(bmclibErrs.ErrLoginFailed, "client not initialized") + } + + return c.serviceClient.redfish.SystemPowerStatus(ctx) +} + +// PowerSet sets the power state of a server +func (c *Client) PowerSet(ctx context.Context, state string) (ok bool, err error) { + return c.serviceClient.redfish.PowerSet(ctx, state) +} + +// Inventory collects hardware inventory and install firmware information +func (c *Client) Inventory(ctx context.Context) (device *common.Device, err error) { + return c.bmc.inventory(ctx) +} + func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) { x11 := newX11Client(c.serviceClient, c.log) x12 := newX12Client(c.serviceClient, c.log) @@ -218,21 +243,6 @@ func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) { return queryor, nil } -func (c *Client) openRedfish(ctx context.Context) error { - if c.serviceClient.redfish != nil && c.serviceClient.redfish.SessionActive() == nil { - return nil - } - - rfclient := redfishwrapper.NewClient(c.serviceClient.host, "", c.serviceClient.user, c.serviceClient.pass) - if err := rfclient.Open(ctx); err != nil { - return err - } - - c.serviceClient.redfish = rfclient - - return nil -} - func parseToken(body []byte) string { var key string if bytes.Contains(body, []byte(`CSRF-TOKEN`)) { @@ -359,28 +369,6 @@ func (c *Client) PowerSet(ctx context.Context, state string) (ok bool, err error } } -// powerCycle using SMC XML API -// -// This method is only here for the case when firmware updates are being applied using this provider. -func (c *Client) powerCycle(ctx context.Context) (bool, error) { - payload := []byte(`op=SET_POWER_INFO.XML&r=(1,3)&_=`) - - headers := map[string]string{ - "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", - } - - body, status, err := c.serviceClient.query(ctx, "cgi/ipmi.cgi", http.MethodPost, bytes.NewBuffer(payload), headers, 0) - if err != nil { - return false, err - } - - if status != http.StatusOK { - return false, unexpectedResponseErr(payload, body, status) - } - - return true, nil -} - type serviceClient struct { host string port string @@ -404,6 +392,10 @@ func (c *serviceClient) setCsrfToken(t string) { } func (c *serviceClient) redfishSession(ctx context.Context) (err error) { + if c.redfish != nil && c.redfish.SessionActive() == nil { + return nil + } + c.redfish = redfishwrapper.NewClient(c.host, "", c.user, c.pass, redfishwrapper.WithHTTPClient(c.client)) if err := c.redfish.Open(ctx); err != nil { return err diff --git a/providers/supermicro/x11.go b/providers/supermicro/x11.go index 08525e7a..e143e721 100644 --- a/providers/supermicro/x11.go +++ b/providers/supermicro/x11.go @@ -143,3 +143,39 @@ func (c *x11) firmwareTaskStatus(ctx context.Context, component, _ string) (stat return "", "", errors.Wrap(bmclibErrs.ErrFirmwareTaskStatus, "component unsupported: "+component) } + +func (c *x11) inventory(ctx context.Context) (*common.Device, error) { + if err := c.redfishSession(ctx); err != nil { + return nil, err + } + + return c.serviceClient.redfish.Inventory(ctx, false) +} + +// power-off - immediate - op=POWER_INFO.XML&r=(1,0)&_= +// power-on - op=POWER_INFO.XML&r=(1,1)&_= +// power-off - acpi/orderly - op=POWER_INFO.XML&r=(1,5)&_= +// reset server - cold powercycle - op=POWER_INFO.XML&r=(1,3)&_= +// power cycle - op=POWER_INFO.XML&r=(1,2)&_= + +// powerCycle using SMC XML API +// +// This method is only here for the case when firmware updates are being applied using this provider. +func (c *x11) powerCycle(ctx context.Context) (bool, error) { + payload := []byte(`op=POWER_INFO.XML&r=(1,3)&_=`) + + headers := map[string]string{ + "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", + } + + body, status, err := c.serviceClient.query(ctx, "cgi/ipmi.cgi", http.MethodPost, bytes.NewBuffer(payload), headers, 0) + if err != nil { + return false, err + } + + if status != http.StatusOK { + return false, unexpectedResponseErr(payload, body, status) + } + + return true, nil +} diff --git a/providers/supermicro/x12.go b/providers/supermicro/x12.go index a48c0006..da5bc547 100644 --- a/providers/supermicro/x12.go +++ b/providers/supermicro/x12.go @@ -301,3 +301,11 @@ func (c *x12) firmwareTaskStatus(ctx context.Context, component, taskID string) return c.redfish.TaskStatus(ctx, taskID) } + +func (c *x12) inventory(ctx context.Context) (*common.Device, error) { + if err := c.redfishSession(ctx); err != nil { + return nil, err + } + + return c.serviceClient.redfish.Inventory(ctx, false) +}