diff --git a/oem/nvidia/mode.go b/oem/nvidia/mode.go new file mode 100644 index 00000000..37c3835b --- /dev/null +++ b/oem/nvidia/mode.go @@ -0,0 +1,77 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package nvidia + +import ( + "context" + "encoding/json" + "errors" + + "github.com/coreweave/gofish/common" +) + +// ModeValue is the operating mode reported by the NVIDIA DPU OEM mode resource. +type ModeValue string + +const ( + // DPUMode configures the device as a DPU. + DPUMode ModeValue = "DpuMode" + // NICMode configures the device as a NIC. + NICMode ModeValue = "NicMode" +) + +// Mode is the NVIDIA OEM mode resource for a DPU system. +type Mode struct { + common.Entity + + Mode ModeValue + + setTarget string +} + +// UnmarshalJSON unmarshals a Mode object from the raw JSON. +func (m *Mode) UnmarshalJSON(b []byte) error { + type temp Mode + var t struct { + temp + Actions struct { + Set common.ActionTarget `json:"#Mode.Set"` + } + } + + err := json.Unmarshal(b, &t) + if err != nil { + return err + } + + *m = Mode(t.temp) + m.setTarget = t.Actions.Set.Target + + return nil +} + +// GetMode will get the Mode instance from the service. +func GetMode(c common.Client, uri string, queryOpts ...common.QueryGroupOption) (*Mode, error) { + return GetModeWithContext(common.ContextOf(c), c, uri, queryOpts...) +} + +// GetModeWithContext will get the Mode instance from the service. +func GetModeWithContext(ctx context.Context, c common.Client, uri string, queryOpts ...common.QueryGroupOption) (*Mode, error) { + return common.GetObjectWithContext[Mode](ctx, c, uri, queryOpts...) +} + +// SetMode performs the Mode.Set action. +func (m *Mode) SetMode(mode ModeValue) error { + return m.SetModeWithContext(common.ContextOf(m.GetClient()), mode) +} + +// SetModeWithContext performs the Mode.Set action. +func (m *Mode) SetModeWithContext(ctx context.Context, mode ModeValue) error { + if m.setTarget == "" { + return errors.New("Mode.Set is not supported by this system") + } + + return m.PostWithContext(ctx, m.setTarget, map[string]any{"Mode": mode}) +} diff --git a/oem/nvidia/mode_test.go b/oem/nvidia/mode_test.go new file mode 100644 index 00000000..7b1e0d9f --- /dev/null +++ b/oem/nvidia/mode_test.go @@ -0,0 +1,83 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package nvidia + +import ( + "encoding/json" + "net/http" + "strings" + "testing" + + "github.com/coreweave/gofish/common" +) + +const modeBody = `{ + "@odata.id": "/redfish/v1/Systems/Bluefield/Oem/Nvidia", + "@odata.type": "#NvidiaMode.v1_0_0.Mode", + "Id": "Nvidia", + "Mode": "NicMode", + "Actions": { + "#Mode.Set": { + "target": "/redfish/v1/Systems/Bluefield/Oem/Nvidia/Actions/AdvertisedMode.Set", + "@Redfish.ActionInfo": "/redfish/v1/Systems/Bluefield/Oem/Nvidia/ModeSetActionInfo" + } + } +}` + +func TestModeUnmarshal(t *testing.T) { + var mode Mode + if err := json.Unmarshal([]byte(modeBody), &mode); err != nil { + t.Fatal(err) + } + + if mode.Mode != NICMode { + t.Fatalf("Mode = %q, want %q", mode.Mode, NICMode) + } + if mode.setTarget != "/redfish/v1/Systems/Bluefield/Oem/Nvidia/Actions/AdvertisedMode.Set" { + t.Fatalf("setTarget = %q", mode.setTarget) + } +} + +func TestModeSetMode(t *testing.T) { + client := &common.TestClient{ + CustomReturnForActions: map[string][]interface{}{ + http.MethodPost: { + &http.Response{StatusCode: http.StatusOK, Body: http.NoBody}, + }, + }, + } + mode := &Mode{setTarget: "/redfish/v1/Systems/Bluefield/Oem/Nvidia/Actions/AdvertisedMode.Set"} + mode.SetClient(client) + + if err := mode.SetMode(DPUMode); err != nil { + t.Fatal(err) + } + + calls := client.CapturedCalls() + if len(calls) != 1 { + t.Fatalf("calls = %d, want 1", len(calls)) + } + if calls[0].Action != http.MethodPost { + t.Fatalf("Action = %q, want %q", calls[0].Action, http.MethodPost) + } + if calls[0].URL != "/redfish/v1/Systems/Bluefield/Oem/Nvidia/Actions/AdvertisedMode.Set" { + t.Fatalf("URL = %q", calls[0].URL) + } + if !strings.Contains(calls[0].Payload, "Mode:DpuMode") { + t.Fatalf("Payload = %q", calls[0].Payload) + } +} + +func TestModeSetModeRequiresActionTarget(t *testing.T) { + mode := &Mode{} + + err := mode.SetMode(DPUMode) + if err == nil { + t.Fatal("SetMode succeeded without an action target") + } + if err.Error() != "Mode.Set is not supported by this system" { + t.Fatalf("error = %q", err) + } +}