Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions rest-api/api/pkg/api/handler/expectedmachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ func (cemh CreateExpectedMachineHandler) Handle(c echo.Context) error {
SlotID: apiRequest.SlotID,
TrayIdx: apiRequest.TrayIdx,
HostID: apiRequest.HostID,
IsDpfEnabled: apiRequest.IsDpfEnabled,
Labels: apiRequest.Labels,
CreatedBy: dbUser.ID,
},
Expand Down Expand Up @@ -703,6 +704,7 @@ func (uemh UpdateExpectedMachineHandler) Handle(c echo.Context) error {
TrayIdx: apiRequest.TrayIdx,
HostID: apiRequest.HostID,
Labels: apiRequest.Labels,
IsDpfEnabled: apiRequest.IsDpfEnabled,
},
)
if err != nil {
Expand Down Expand Up @@ -1113,6 +1115,7 @@ func (cemh CreateExpectedMachinesHandler) Handle(c echo.Context) error {
TrayIdx: machineReq.TrayIdx,
HostID: machineReq.HostID,
Labels: machineReq.Labels,
IsDpfEnabled: machineReq.IsDpfEnabled,
CreatedBy: dbUser.ID,
})
}
Expand Down Expand Up @@ -1548,6 +1551,7 @@ func (uemh UpdateExpectedMachinesHandler) Handle(c echo.Context) error {
TrayIdx: machineReq.TrayIdx,
HostID: machineReq.HostID,
Labels: machineReq.Labels,
IsDpfEnabled: machineReq.IsDpfEnabled,
})
}

Expand Down
77 changes: 77 additions & 0 deletions rest-api/api/pkg/api/handler/expectedmachine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2172,6 +2172,83 @@ func TestCreateExpectedMachineHandler_BmcCredentialsForwardedToWorkflow(t *testi
}
}

func TestCreateExpectedMachineHandler_DpfEnabledForwardedToWorkflow(t *testing.T) {
e := echo.New()
dbSession := testExpectedMachineInitDB(t)
defer dbSession.Close()

cfg := common.GetTestConfig()
tcfg, _ := cfg.GetTemporalConfig()
scp := sc.NewClientPool(tcfg)

org := "test-org"
_, site := testExpectedMachineSetupTestData(t, dbSession, org)

var capturedRequest *cwssaws.ExpectedMachine
mockTemporalClient := &tmocks.Client{}
mockWorkflowRun := &tmocks.WorkflowRun{}
mockWorkflowRun.On("GetID").Return("test-workflow-id")
mockWorkflowRun.Mock.On("Get", mock.Anything, mock.Anything).Return(nil)
mockTemporalClient.Mock.On("ExecuteWorkflow", mock.Anything, mock.Anything, "CreateExpectedMachine", mock.Anything).
Run(func(args mock.Arguments) {
if req, ok := args.Get(3).(*cwssaws.ExpectedMachine); ok {
capturedRequest = req
}
}).
Return(mockWorkflowRun, nil)
scp.IDClientMap[site.ID.String()] = mockTemporalClient

handler := NewCreateExpectedMachineHandler(dbSession, scp, cfg)

rawBody := map[string]interface{}{
"siteId": site.ID.String(),
"bmcMacAddress": "00:AA:BB:CC:DD:EF",
"chassisSerialNumber": "DPF-TEST-CHASSIS-001",
"isDpfEnabled": false,
}
reqBody, err := json.Marshal(rawBody)
assert.Nil(t, err)

req := httptest.NewRequest(http.MethodPost, "/v2/org/"+org+"/nico/expected-machine", bytes.NewReader(reqBody))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
req = req.WithContext(context.Background())

rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.Set("user", &cdbm.User{
StarfleetID: cutil.GetPtr("test-user"),
OrgData: cdbm.OrgData{
org: cdbm.Org{
ID: 123,
Name: org,
DisplayName: org,
OrgType: "ENTERPRISE",
Roles: []string{"FORGE_PROVIDER_ADMIN"},
},
},
})
c.SetParamNames("orgName")
c.SetParamValues(org)

err = handler.Handle(c)
assert.Nil(t, err)
assert.Equal(t, http.StatusCreated, rec.Code, "Response: %s", rec.Body.String())

if assert.NotNil(t, capturedRequest, "workflow should have received a request") {
if assert.NotNil(t, capturedRequest.IsDpfEnabled) {
assert.False(t, *capturedRequest.IsDpfEnabled)
}
assert.False(t, capturedRequest.DpfEnabled)
}

var apiResponse model.APIExpectedMachine
err = json.Unmarshal(rec.Body.Bytes(), &apiResponse)
assert.Nil(t, err)
if assert.NotNil(t, apiResponse.IsDpfEnabled) {
assert.False(t, *apiResponse.IsDpfEnabled)
}
}

// TestUpdateExpectedMachineHandler_BmcCredentialsForwardedToWorkflow is a regression test for the
// same spec / JSON struct tag mismatch bug described in
// TestCreateExpectedMachineHandler_BmcCredentialsForwardedToWorkflow, but for the PATCH
Expand Down
7 changes: 7 additions & 0 deletions rest-api/api/pkg/api/model/expectedmachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ type APIExpectedMachineCreateRequest struct {
TrayIdx *int32 `json:"trayIdx"`
// HostID is the optional host identifier
HostID *int32 `json:"hostId"`
// IsDpfEnabled marks whether this host is eligible for DPF-based provisioning
IsDpfEnabled *bool `json:"isDpfEnabled"`
// Labels is the labels of the expected machine
Labels map[string]string `json:"labels"`
}
Expand Down Expand Up @@ -138,6 +140,8 @@ type APIExpectedMachineUpdateRequest struct {
TrayIdx *int32 `json:"trayIdx"`
// HostID is the optional host identifier
HostID *int32 `json:"hostId"`
// IsDpfEnabled marks whether this host is eligible for DPF-based provisioning
IsDpfEnabled *bool `json:"isDpfEnabled"`
// Labels is the labels of the expected machine
Labels map[string]string `json:"labels"`
}
Expand Down Expand Up @@ -246,6 +250,8 @@ type APIExpectedMachine struct {
TrayIdx *int32 `json:"trayIdx"`
// HostID is the optional host identifier
HostID *int32 `json:"hostId"`
// IsDpfEnabled indicates whether this host is eligible for DPF-based provisioning
IsDpfEnabled *bool `json:"isDpfEnabled"`
// Labels is the labels of the expected machine
Labels map[string]string `json:"labels"`
// Created indicates the ISO datetime string for when the ExpectedMachine was created
Expand Down Expand Up @@ -273,6 +279,7 @@ func NewAPIExpectedMachine(dibp *cdbm.ExpectedMachine) *APIExpectedMachine {
SlotID: dibp.SlotID,
TrayIdx: dibp.TrayIdx,
HostID: dibp.HostID,
IsDpfEnabled: dibp.IsDpfEnabled,
Labels: dibp.Labels,
Created: dibp.Created,
Updated: dibp.Updated,
Expand Down
21 changes: 21 additions & 0 deletions rest-api/api/pkg/api/model/expectedmachine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,27 @@ func TestNewAPIExpectedMachine(t *testing.T) {
}
}

func TestNewAPIExpectedMachine_DpfEnabled(t *testing.T) {
t.Run("nil stored value defaults to true", func(t *testing.T) {
got := NewAPIExpectedMachine(&cdbm.ExpectedMachine{})
assert.Nil(t, got.IsDpfEnabled)
})

t.Run("stored false is returned", func(t *testing.T) {
got := NewAPIExpectedMachine(&cdbm.ExpectedMachine{
IsDpfEnabled: cutil.GetPtr(false),
})
assert.False(t, *got.IsDpfEnabled)
})

t.Run("stored true is returned", func(t *testing.T) {
got := NewAPIExpectedMachine(&cdbm.ExpectedMachine{
IsDpfEnabled: cutil.GetPtr(true),
})
assert.True(t, *got.IsDpfEnabled)
})
}

func TestNewAPIExpectedMachineWithNilFields(t *testing.T) {
dbEM := &cdbm.ExpectedMachine{
BmcMacAddress: "00:11:22:33:44:55",
Expand Down
13 changes: 12 additions & 1 deletion rest-api/db/pkg/db/model/expectedmachine.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type ExpectedMachine struct {
SlotID *int32 `bun:"slot_id"`
TrayIdx *int32 `bun:"tray_idx"`
HostID *int32 `bun:"host_id"`
IsDpfEnabled *bool `bun:"is_dpf_enabled"`
Labels Labels `bun:"labels,type:jsonb"`
Created time.Time `bun:"created,nullzero,notnull,default:current_timestamp"`
Updated time.Time `bun:"updated,nullzero,notnull,default:current_timestamp"`
Expand Down Expand Up @@ -119,7 +120,9 @@ func (em *ExpectedMachine) ToProto(creds ExpectedMachineCredentials) *cwssaws.Ex
if em.HostID != nil {
proto.HostId = em.HostID
}

if em.IsDpfEnabled != nil {
proto.IsDpfEnabled = em.IsDpfEnabled
}
if creds.Username != nil {
proto.BmcUsername = *creds.Username
}
Expand Down Expand Up @@ -183,6 +186,7 @@ func (em *ExpectedMachine) FromProto(proto *cwssaws.ExpectedMachine, linkedMachi
em.TrayIdx = proto.TrayIdx
em.HostID = proto.HostId
em.Labels.FromProto(proto.Metadata.GetLabels())
em.IsDpfEnabled = proto.IsDpfEnabled
}

// ExpectedMachineCreateInput input parameters for Create method
Expand All @@ -204,6 +208,7 @@ type ExpectedMachineCreateInput struct {
TrayIdx *int32
HostID *int32
Labels map[string]string
IsDpfEnabled *bool
CreatedBy uuid.UUID
}

Expand All @@ -225,6 +230,7 @@ type ExpectedMachineUpdateInput struct {
TrayIdx *int32
HostID *int32
Labels map[string]string
IsDpfEnabled *bool
}

// ExpectedMachineClearInput input parameters for Clear method
Expand Down Expand Up @@ -366,6 +372,7 @@ func (emsd ExpectedMachineSQLDAO) CreateMultiple(ctx context.Context, tx *db.Tx,
TrayIdx: input.TrayIdx,
HostID: input.HostID,
Labels: input.Labels,
IsDpfEnabled: input.IsDpfEnabled,
CreatedBy: input.CreatedBy,
}
expectedMachines = append(expectedMachines, em)
Expand Down Expand Up @@ -663,6 +670,10 @@ func (emsd ExpectedMachineSQLDAO) UpdateMultiple(ctx context.Context, tx *db.Tx,
em.HostID = input.HostID
columnsSet["host_id"] = true
}
if input.IsDpfEnabled != nil {
em.IsDpfEnabled = input.IsDpfEnabled
columnsSet["is_dpf_enabled"] = true
}

expectedMachines = append(expectedMachines, em)
ids = append(ids, input.ExpectedMachineID)
Expand Down
56 changes: 56 additions & 0 deletions rest-api/db/pkg/db/model/expectedmachine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ func TestExpectedMachine_FromProto(t *testing.T) {
assert.Equal(t, Labels{"env": "prod"}, em.Labels)
})

t.Run("populates dpfEnabled from is_dpf_enabled", func(t *testing.T) {
em := &ExpectedMachine{}
enabled := false
em.FromProto(&cwssaws.ExpectedMachine{
Id: &cwssaws.UUID{Value: id.String()},
BmcMacAddress: "aa:bb",
IsDpfEnabled: &enabled,
}, nil)

if assert.NotNil(t, em.IsDpfEnabled) {
assert.False(t, *em.IsDpfEnabled)
}
})

t.Run("nil linkedMachineID leaves MachineID nil", func(t *testing.T) {
em := &ExpectedMachine{}
em.FromProto(&cwssaws.ExpectedMachine{
Expand All @@ -118,6 +132,48 @@ func TestExpectedMachine_FromProto(t *testing.T) {
})
}

func TestExpectedMachine_ToProto(t *testing.T) {
id := uuid.New()
enabled := true
disabled := false

t.Run("sets is_dpf_enabled when stored", func(t *testing.T) {
em := &ExpectedMachine{
ID: id,
BmcMacAddress: "aa:bb:cc:dd:ee:ff",
ChassisSerialNumber: "CSN-1",
IsDpfEnabled: &enabled,
}
proto := em.ToProto(ExpectedMachineCredentials{})
assert.True(t, *proto.IsDpfEnabled)
})

t.Run("omits is_dpf_enabled when unset", func(t *testing.T) {
em := &ExpectedMachine{
ID: id,
BmcMacAddress: "aa:bb:cc:dd:ee:ff",
ChassisSerialNumber: "CSN-1",
}
proto := em.ToProto(ExpectedMachineCredentials{})
assert.Nil(t, proto.IsDpfEnabled)
assert.False(t, proto.DpfEnabled)
})

t.Run("forwards false value", func(t *testing.T) {
em := &ExpectedMachine{
ID: id,
BmcMacAddress: "aa:bb:cc:dd:ee:ff",
ChassisSerialNumber: "CSN-1",
IsDpfEnabled: &disabled,
}
proto := em.ToProto(ExpectedMachineCredentials{})
if assert.NotNil(t, proto.IsDpfEnabled) {
assert.False(t, *proto.IsDpfEnabled)
}
assert.False(t, proto.DpfEnabled)
})
}

// reset the tables needed for ExpectedMachine tests
func testExpectedMachineSetupSchema(t *testing.T, dbSession *db.Session) {
ctx := context.Background()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package migrations

import (
"context"
"database/sql"
"fmt"

"github.com/uptrace/bun"
)

func init() {
Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
tx, terr := db.BeginTx(ctx, &sql.TxOptions{})
if terr != nil {
handlePanic(terr, "failed to begin transaction")
}

_, err := tx.Exec("ALTER TABLE expected_machine ADD COLUMN IF NOT EXISTS is_dpf_enabled BOOLEAN")
handleError(tx, err)

terr = tx.Commit()
if terr != nil {
handlePanic(terr, "failed to commit transaction")
}

fmt.Print(" [up migration] Added is_dpf_enabled column to 'expected_machine'. ")
return nil
}, func(ctx context.Context, db *bun.DB) error {
fmt.Print(" [down migration] No action taken")
return nil
})
}
Loading
Loading