Skip to content

Commit

Permalink
Update virtual media mounting: (#398)
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Oct 9, 2024
2 parents e0bb584 + 8c2a500 commit eba5356
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 36 deletions.
18 changes: 18 additions & 0 deletions examples/virtualmedia/doc.go
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions examples/virtualmedia/main.go
Original file line number Diff line number Diff line change
@@ -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())
}
67 changes: 31 additions & 36 deletions internal/redfishwrapper/virtual_media.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -29,50 +33,41 @@ 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 {
if len(virtualMedia) == 0 {
return false, errors.New("no virtual media found")
}

for _, vm := range virtualMedia {
if vm.Inserted {
if err := vm.EjectMedia(); err != nil {
return false, err
}
}
}
}

// 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
if mediaURL == "" {
// Only ejecting the media was requested.
return true, nil
}

for _, media := range virtualMedia {
for _, t := range media.MediaTypes {
if t == mediaKind {
err = media.InsertMedia(mediaURL, true, true)
if err != nil {
return false, err
}
setMedia = true
break
}
if !slices.Contains(vm.MediaTypes, mediaKind) {
return false, fmt.Errorf("media kind %s not supported by BMC, supported media kinds %q", kind, vm.MediaTypes)
}
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
}
}
}
if !setMedia {
return false, fmt.Errorf("media kind %s not supported", kind)
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) {
Expand All @@ -83,8 +78,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
}
Expand Down

0 comments on commit eba5356

Please sign in to comment.