From fd1660dad40cdf2cb33e0e825bd60b51c19a39aa Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Mon, 7 Oct 2024 20:58:08 -0600 Subject: [PATCH 1/3] Update virtual media mounting: At least one BMC, Supermicro SYS-E300-D9, did not support setting inserted and/or writeProtected properties in redfish calls to do a virtual media mount. This falls back to not using them if the initial call with them in the properties fails. This was test and worked successfully on a Supermicro (SYS-E300-D9), HP ILO5, and Dell iDRAC9. Signed-off-by: Jacob Weinstock --- internal/redfishwrapper/virtual_media.go | 65 +++++++++++------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/internal/redfishwrapper/virtual_media.go b/internal/redfishwrapper/virtual_media.go index 562cd8fa..8a99eaa5 100644 --- a/internal/redfishwrapper/virtual_media.go +++ b/internal/redfishwrapper/virtual_media.go @@ -2,18 +2,22 @@ package redfishwrapper import ( "context" + "errors" "fmt" + "slices" - "github.com/pkg/errors" rf "github.com/stmcginnis/gofish/redfish" ) // Set the virtual media attached to the system, or just eject everything if mediaURL is empty. -func (c *Client) SetVirtualMedia(ctx context.Context, kind string, mediaURL string) (ok bool, err error) { +func (c *Client) SetVirtualMedia(ctx context.Context, kind string, mediaURL string) (bool, error) { managers, err := c.Managers(ctx) if err != nil { return false, err } + if len(managers) == 0 { + return false, errors.New("no redfish managers found") + } var mediaKind rf.VirtualMediaType switch kind { @@ -29,50 +33,43 @@ func (c *Client) SetVirtualMedia(ctx context.Context, kind string, mediaURL stri return false, errors.New("invalid media type") } - for _, manager := range managers { - virtualMedia, err := manager.VirtualMedia() + for _, m := range managers { + virtualMedia, err := m.VirtualMedia() if err != nil { return false, err } - for _, media := range virtualMedia { - if media.Inserted { - err = media.EjectMedia() - if err != nil { - return false, err - } - } + if len(virtualMedia) == 0 { + return false, errors.New("no virtual media found") } - } - // An empty mediaURL means eject everything, so if that's the case we're done. Otherwise, we - // need to insert the media. - if mediaURL != "" { - setMedia := false - for _, manager := range managers { - virtualMedia, err := manager.VirtualMedia() - if err != nil { - return false, err + for _, vm := range virtualMedia { + if vm.Inserted { + if err := vm.EjectMedia(); err != nil { + return false, err + } } - - for _, media := range virtualMedia { - for _, t := range media.MediaTypes { - if t == mediaKind { - err = media.InsertMedia(mediaURL, true, true) - if err != nil { + if mediaURL != "" { + if slices.Contains(vm.MediaTypes, mediaKind) { + if err := vm.InsertMedia(mediaURL, true, true); err != nil { + // Some BMC's (Supermicro SYS-E300-D9, for example) don't support the "inserted" and "writeProtected" properties, + // so we try to insert the media without them if the first attempt fails. + if err := vm.InsertMediaConfig(rf.VirtualMediaConfig{Image: mediaURL}); err != nil { return false, err } - setMedia = true - break } + return true, nil } + + return false, fmt.Errorf("media kind %s not supported by BMC, supported media kinds %q", kind, vm.MediaTypes) } - } - if !setMedia { - return false, fmt.Errorf("media kind %s not supported", kind) + + // Only ejecting the media was requested. + return true, nil } } - return true, nil + // If we actual get here, then something very unexpected happened as there isn't a known code path that would cause this error to be returned. + return false, errors.New("unexpected error setting virtual media") } func (c *Client) InsertedVirtualMedia(ctx context.Context) ([]string, error) { @@ -83,8 +80,8 @@ func (c *Client) InsertedVirtualMedia(ctx context.Context) ([]string, error) { var inserted []string - for _, manager := range managers { - virtualMedia, err := manager.VirtualMedia() + for _, m := range managers { + virtualMedia, err := m.VirtualMedia() if err != nil { return nil, err } From aa8f53a6ddf7fa1582793cef6696399028131cc1 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Tue, 8 Oct 2024 10:12:31 -0600 Subject: [PATCH 2/3] Return early for improved code clarity: This helps understand and maintain-ability. Decreases the complexity of the function too. Signed-off-by: Jacob Weinstock --- internal/redfishwrapper/virtual_media.go | 26 +++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/internal/redfishwrapper/virtual_media.go b/internal/redfishwrapper/virtual_media.go index 8a99eaa5..5ec83a90 100644 --- a/internal/redfishwrapper/virtual_media.go +++ b/internal/redfishwrapper/virtual_media.go @@ -48,22 +48,20 @@ func (c *Client) SetVirtualMedia(ctx context.Context, kind string, mediaURL stri return false, err } } - if mediaURL != "" { - if slices.Contains(vm.MediaTypes, mediaKind) { - if err := vm.InsertMedia(mediaURL, true, true); err != nil { - // Some BMC's (Supermicro SYS-E300-D9, for example) don't support the "inserted" and "writeProtected" properties, - // so we try to insert the media without them if the first attempt fails. - if err := vm.InsertMediaConfig(rf.VirtualMediaConfig{Image: mediaURL}); err != nil { - return false, err - } - } - return true, nil - } - + if mediaURL == "" { + // Only ejecting the media was requested. + return true, nil + } + if !slices.Contains(vm.MediaTypes, mediaKind) { return false, fmt.Errorf("media kind %s not supported by BMC, supported media kinds %q", kind, vm.MediaTypes) } - - // Only ejecting the media was requested. + if err := vm.InsertMedia(mediaURL, true, true); err != nil { + // Some BMC's (Supermicro X11SDV-4C-TLN2F, for example) don't support the "inserted" and "writeProtected" properties, + // so we try to insert the media without them if the first attempt fails. + if err := vm.InsertMediaConfig(rf.VirtualMediaConfig{Image: mediaURL}); err != nil { + return false, err + } + } return true, nil } } From 8c2a5009265f642d1e0b17df446c9dcae868d24f Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Tue, 8 Oct 2024 11:27:54 -0600 Subject: [PATCH 3/3] Add example for virtual media: This helps users see how to use the virtual media mounting capabilities. Signed-off-by: Jacob Weinstock --- examples/virtualmedia/doc.go | 18 +++++++++++++ examples/virtualmedia/main.go | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 examples/virtualmedia/doc.go create mode 100644 examples/virtualmedia/main.go diff --git a/examples/virtualmedia/doc.go b/examples/virtualmedia/doc.go new file mode 100644 index 00000000..2dd0ef3a --- /dev/null +++ b/examples/virtualmedia/doc.go @@ -0,0 +1,18 @@ +/* +Virtual Media is an example command to mount and umount virtual media (ISO) on a BMC. + + # mount an ISO + $ go run examples/virtualmedia/main.go \ + -host 10.1.2.3 \ + -user root \ + -password calvin \ + -iso http://example.com/image.iso + + # unmount an ISO + $ go run examples/virtualmedia/main.go \ + -host 10.1.2.3 \ + -user root \ + -password calvin \ + -iso "" +*/ +package main diff --git a/examples/virtualmedia/main.go b/examples/virtualmedia/main.go new file mode 100644 index 00000000..6dd0ec14 --- /dev/null +++ b/examples/virtualmedia/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log/slog" + "os" + "time" + + "github.com/bmc-toolbox/bmclib/v2" + "github.com/go-logr/logr" +) + +func main() { + + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + + user := flag.String("user", "", "BMC username, required") + pass := flag.String("password", "", "BMC password, required") + host := flag.String("host", "", "BMC hostname or IP address, required") + isoURL := flag.String("iso", "", "The HTTP URL to the ISO to be mounted, leave empty to unmount") + flag.Parse() + + if *user == "" || *pass == "" || *host == "" { + fmt.Fprintln(os.Stderr, "user, password, and host are required") + flag.PrintDefaults() + os.Exit(1) + } + + l := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true})) + log := logr.FromSlogHandler(l.Handler()) + + cl := bmclib.NewClient(*host, *user, *pass, bmclib.WithLogger(log)) + if err := cl.Open(ctx); err != nil { + panic(err) + } + defer cl.Close(ctx) + + ok, err := cl.SetVirtualMedia(ctx, "CD", *isoURL) + if err != nil { + log.Info("debugging", "metadata", cl.GetMetadata()) + panic(err) + } + if !ok { + log.Info("debugging", "metadata", cl.GetMetadata()) + panic("failed virtual media operation") + } + log.Info("virtual media operation successful", "metadata", cl.GetMetadata()) +}