diff --git a/api/pkg/api/handler/expectedmachine.go b/api/pkg/api/handler/expectedmachine.go
index feb0ca5a6..c8dde0951 100644
--- a/api/pkg/api/handler/expectedmachine.go
+++ b/api/pkg/api/handler/expectedmachine.go
@@ -228,6 +228,7 @@ func (cemh CreateExpectedMachineHandler) Handle(c echo.Context) error {
ExpectedMachineID: uuid.New(),
SiteID: site.ID,
BmcMacAddress: apiRequest.BmcMacAddress,
+ BmcIpAddress: apiRequest.BmcIpAddress,
ChassisSerialNumber: apiRequest.ChassisSerialNumber,
SkuID: apiRequest.SkuID,
FallbackDpuSerialNumbers: apiRequest.FallbackDPUSerialNumbers,
@@ -681,6 +682,7 @@ func (uemh UpdateExpectedMachineHandler) Handle(c echo.Context) error {
cdbm.ExpectedMachineUpdateInput{
ExpectedMachineID: expectedMachine.ID,
BmcMacAddress: apiRequest.BmcMacAddress,
+ BmcIpAddress: apiRequest.BmcIpAddress,
ChassisSerialNumber: apiRequest.ChassisSerialNumber,
SkuID: apiRequest.SkuID,
FallbackDpuSerialNumbers: apiRequest.FallbackDPUSerialNumbers,
@@ -1099,6 +1101,7 @@ func (cemh CreateExpectedMachinesHandler) Handle(c echo.Context) error {
ExpectedMachineID: id,
SiteID: site.ID,
BmcMacAddress: machineReq.BmcMacAddress,
+ BmcIpAddress: machineReq.BmcIpAddress,
ChassisSerialNumber: machineReq.ChassisSerialNumber,
SkuID: machineReq.SkuID,
FallbackDpuSerialNumbers: machineReq.FallbackDPUSerialNumbers,
@@ -1539,6 +1542,7 @@ func (uemh UpdateExpectedMachinesHandler) Handle(c echo.Context) error {
updateInputs = append(updateInputs, cdbm.ExpectedMachineUpdateInput{
ExpectedMachineID: emID,
BmcMacAddress: machineReq.BmcMacAddress,
+ BmcIpAddress: machineReq.BmcIpAddress,
ChassisSerialNumber: machineReq.ChassisSerialNumber,
SkuID: machineReq.SkuID,
FallbackDpuSerialNumbers: machineReq.FallbackDPUSerialNumbers,
diff --git a/api/pkg/api/handler/expectedmachine_test.go b/api/pkg/api/handler/expectedmachine_test.go
index b7d1c0b6b..7fa42320f 100644
--- a/api/pkg/api/handler/expectedmachine_test.go
+++ b/api/pkg/api/handler/expectedmachine_test.go
@@ -337,6 +337,7 @@ func TestCreateExpectedMachineHandler_Handle(t *testing.T) {
DefaultBmcPassword: cdb.GetStrPtr("password"),
ChassisSerialNumber: "CHASSIS-RACK-001",
RackID: cdb.GetStrPtr("test-rack-001"),
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.10"),
Labels: map[string]string{"env": "rack-test"},
},
setupContext: func(c echo.Context) {
@@ -406,6 +407,11 @@ func TestCreateExpectedMachineHandler_Handle(t *testing.T) {
assert.Equal(t, *tt.requestBody.RackID, *response.RackID, "RackID in response should match request")
}
}
+ if tt.requestBody.BmcIpAddress != nil {
+ if assert.NotNil(t, response.BmcIpAddress, "BmcIpAddress should not be nil in response") {
+ assert.Equal(t, *tt.requestBody.BmcIpAddress, *response.BmcIpAddress, "BmcIpAddress in response should match request")
+ }
+ }
}
})
}
diff --git a/api/pkg/api/handler/expectedpowershelf.go b/api/pkg/api/handler/expectedpowershelf.go
index f9bbd7202..95d91f534 100644
--- a/api/pkg/api/handler/expectedpowershelf.go
+++ b/api/pkg/api/handler/expectedpowershelf.go
@@ -165,7 +165,7 @@ func (cepsh CreateExpectedPowerShelfHandler) Handle(c echo.Context) error {
SiteID: site.ID,
BmcMacAddress: apiRequest.BmcMacAddress,
ShelfSerialNumber: apiRequest.ShelfSerialNumber,
- IpAddress: apiRequest.IpAddress,
+ BmcIpAddress: apiRequest.BmcIpAddress,
RackID: apiRequest.RackID,
Name: apiRequest.Name,
Manufacturer: apiRequest.Manufacturer,
@@ -603,7 +603,7 @@ func (uepsh UpdateExpectedPowerShelfHandler) Handle(c echo.Context) error {
ExpectedPowerShelfID: expectedPowerShelf.ID,
BmcMacAddress: apiRequest.BmcMacAddress,
ShelfSerialNumber: apiRequest.ShelfSerialNumber,
- IpAddress: apiRequest.IpAddress,
+ BmcIpAddress: apiRequest.BmcIpAddress,
RackID: apiRequest.RackID,
Name: apiRequest.Name,
Manufacturer: apiRequest.Manufacturer,
diff --git a/api/pkg/api/handler/expectedpowershelf_test.go b/api/pkg/api/handler/expectedpowershelf_test.go
index a90576658..df3b64112 100644
--- a/api/pkg/api/handler/expectedpowershelf_test.go
+++ b/api/pkg/api/handler/expectedpowershelf_test.go
@@ -161,7 +161,7 @@ func TestCreateExpectedPowerShelfHandler_Handle(t *testing.T) {
}
}
- validIpAddress := "192.168.1.100"
+ validBmcIpAddress := "192.168.1.100"
tests := []struct {
name string
@@ -177,7 +177,7 @@ func TestCreateExpectedPowerShelfHandler_Handle(t *testing.T) {
DefaultBmcUsername: cdb.GetStrPtr("admin"),
DefaultBmcPassword: cdb.GetStrPtr("password"),
ShelfSerialNumber: "SHELF123",
- IpAddress: &validIpAddress,
+ BmcIpAddress: &validBmcIpAddress,
Labels: map[string]string{"env": "test"},
},
setupContext: func(c echo.Context) {
diff --git a/api/pkg/api/handler/expectedswitch.go b/api/pkg/api/handler/expectedswitch.go
index 920dacdcf..4d1039f6f 100644
--- a/api/pkg/api/handler/expectedswitch.go
+++ b/api/pkg/api/handler/expectedswitch.go
@@ -164,6 +164,7 @@ func (cesh CreateExpectedSwitchHandler) Handle(c echo.Context) error {
ExpectedSwitchID: uuid.New(),
SiteID: site.ID,
BmcMacAddress: apiRequest.BmcMacAddress,
+ BmcIpAddress: apiRequest.BmcIpAddress,
SwitchSerialNumber: apiRequest.SwitchSerialNumber,
RackID: apiRequest.RackID,
Name: apiRequest.Name,
@@ -603,6 +604,7 @@ func (uesh UpdateExpectedSwitchHandler) Handle(c echo.Context) error {
cdbm.ExpectedSwitchUpdateInput{
ExpectedSwitchID: expectedSwitch.ID,
BmcMacAddress: apiRequest.BmcMacAddress,
+ BmcIpAddress: apiRequest.BmcIpAddress,
SwitchSerialNumber: apiRequest.SwitchSerialNumber,
RackID: apiRequest.RackID,
Name: apiRequest.Name,
diff --git a/api/pkg/api/handler/expectedswitch_test.go b/api/pkg/api/handler/expectedswitch_test.go
index 2f9fba449..f328a7451 100644
--- a/api/pkg/api/handler/expectedswitch_test.go
+++ b/api/pkg/api/handler/expectedswitch_test.go
@@ -177,6 +177,7 @@ func TestCreateExpectedSwitchHandler_Handle(t *testing.T) {
SwitchSerialNumber: "SWITCH123",
NvOsUsername: cdb.GetStrPtr("nvos-admin"),
NvOsPassword: cdb.GetStrPtr("nvos-password"),
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.10"),
Labels: map[string]string{"env": "test"},
},
setupContext: func(c echo.Context) {
@@ -290,12 +291,19 @@ func TestCreateExpectedSwitchHandler_Handle(t *testing.T) {
t.Errorf("Response: %v", rec.Body.String())
}
- if tt.expectedStatus == http.StatusCreated && tt.requestBody.Labels != nil {
+ if tt.expectedStatus == http.StatusCreated {
var response model.APIExpectedSwitch
err := json.Unmarshal(rec.Body.Bytes(), &response)
assert.Nil(t, err)
- assert.NotNil(t, response.Labels, "Labels should not be nil in response")
- assert.Equal(t, tt.requestBody.Labels, response.Labels, "Labels in response should match request")
+ if tt.requestBody.Labels != nil {
+ assert.NotNil(t, response.Labels, "Labels should not be nil in response")
+ assert.Equal(t, tt.requestBody.Labels, response.Labels, "Labels in response should match request")
+ }
+ if tt.requestBody.BmcIpAddress != nil {
+ if assert.NotNil(t, response.BmcIpAddress, "BmcIpAddress should not be nil in response") {
+ assert.Equal(t, *tt.requestBody.BmcIpAddress, *response.BmcIpAddress, "BmcIpAddress in response should match request")
+ }
+ }
}
})
}
@@ -735,6 +743,19 @@ func TestUpdateExpectedSwitchHandler_Handle(t *testing.T) {
},
expectedStatus: http.StatusOK,
},
+ {
+ name: "successful update with BmcIpAddress",
+ id: testES.ID.String(),
+ requestBody: model.APIExpectedSwitchUpdateRequest{
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.42"),
+ },
+ setupContext: func(c echo.Context) {
+ c.Set("user", createMockUser(org))
+ c.SetParamNames("orgName", "id")
+ c.SetParamValues(org, testES.ID.String())
+ },
+ expectedStatus: http.StatusOK,
+ },
{
name: "body ID mismatch with URL should return 400",
id: testES.ID.String(),
@@ -787,6 +808,16 @@ func TestUpdateExpectedSwitchHandler_Handle(t *testing.T) {
if tt.expectedStatus != rec.Code {
t.Errorf("Response: %v", rec.Body.String())
}
+
+ // Verify BmcIpAddress round-trips through the update response when set
+ if tt.expectedStatus == http.StatusOK && tt.requestBody.BmcIpAddress != nil {
+ var response model.APIExpectedSwitch
+ err := json.Unmarshal(rec.Body.Bytes(), &response)
+ assert.Nil(t, err)
+ if assert.NotNil(t, response.BmcIpAddress, "BmcIpAddress should not be nil in response") {
+ assert.Equal(t, *tt.requestBody.BmcIpAddress, *response.BmcIpAddress, "BmcIpAddress in response should match request")
+ }
+ }
})
}
}
diff --git a/api/pkg/api/model/expectedmachine.go b/api/pkg/api/model/expectedmachine.go
index 0af1f12f3..ac4e4aae4 100644
--- a/api/pkg/api/model/expectedmachine.go
+++ b/api/pkg/api/model/expectedmachine.go
@@ -53,6 +53,8 @@ type APIExpectedMachineCreateRequest struct {
SkuID *string `json:"skuId"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
+ // BmcIpAddress is the optional BMC IP address of the expected machine
+ BmcIpAddress *string `json:"bmcIpAddress"`
// Name is the optional name of the expected machine
Name *string `json:"name"`
// Manufacturer is the optional manufacturer of the expected machine
@@ -93,6 +95,10 @@ func (emcr *APIExpectedMachineCreateRequest) Validate() error {
validation.NilOrNotEmpty.Error("SkuID cannot be empty")),
validation.Field(&emcr.RackID,
validation.NilOrNotEmpty.Error("RackID cannot be empty")),
+ validation.Field(&emcr.BmcIpAddress,
+ validation.NilOrNotEmpty.Error("BmcIpAddress cannot be empty"),
+ validation.When(emcr.BmcIpAddress != nil && *emcr.BmcIpAddress != "",
+ validationis.IP.Error("BmcIpAddress must be a valid IPv4 or IPv6 address"))),
validation.Field(&emcr.Name,
validation.NilOrNotEmpty.Error("Name cannot be empty")),
validation.Field(&emcr.Manufacturer,
@@ -134,6 +140,8 @@ type APIExpectedMachineUpdateRequest struct {
SkuID *string `json:"skuId"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
+ // BmcIpAddress is the optional BMC IP address of the expected machine
+ BmcIpAddress *string `json:"bmcIpAddress"`
// Name is the optional name of the expected machine
Name *string `json:"name"`
// Manufacturer is the optional manufacturer of the expected machine
@@ -189,6 +197,10 @@ func (emur *APIExpectedMachineUpdateRequest) Validate() error {
validation.NilOrNotEmpty.Error("SkuID cannot be empty")),
validation.Field(&emur.RackID,
validation.NilOrNotEmpty.Error("RackID cannot be empty")),
+ validation.Field(&emur.BmcIpAddress,
+ validation.NilOrNotEmpty.Error("BmcIpAddress cannot be empty"),
+ validation.When(emur.BmcIpAddress != nil && *emur.BmcIpAddress != "",
+ validationis.IP.Error("BmcIpAddress must be a valid IPv4 or IPv6 address"))),
validation.Field(&emur.Name,
validation.NilOrNotEmpty.Error("Name cannot be empty")),
validation.Field(&emur.Manufacturer,
@@ -236,6 +248,8 @@ type APIExpectedMachine struct {
Machine *APIMachineSummary `json:"machine,omitempty"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
+ // BmcIpAddress is the optional BMC IP address of the expected machine
+ BmcIpAddress *string `json:"bmcIpAddress"`
// Name is the optional name of the expected machine
Name *string `json:"name"`
// Manufacturer is the optional manufacturer of the expected machine
@@ -271,6 +285,7 @@ func NewAPIExpectedMachine(dibp *cdbm.ExpectedMachine) *APIExpectedMachine {
SkuID: dibp.SkuID,
MachineID: dibp.MachineID,
RackID: dibp.RackID,
+ BmcIpAddress: dibp.BmcIpAddress,
Name: dibp.Name,
Manufacturer: dibp.Manufacturer,
Model: dibp.Model,
diff --git a/api/pkg/api/model/expectedmachine_test.go b/api/pkg/api/model/expectedmachine_test.go
index ff4ac2f00..238b6c39f 100644
--- a/api/pkg/api/model/expectedmachine_test.go
+++ b/api/pkg/api/model/expectedmachine_test.go
@@ -221,6 +221,62 @@ func TestAPIExpectedMachineCreateRequest_Validate(t *testing.T) {
},
expectErr: true,
},
+ // BmcIpAddress validation tests
+ {
+ desc: "valid IPv4 BmcIpAddress",
+ obj: APIExpectedMachineCreateRequest{
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ChassisSerialNumber: validChassisSerial,
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.10"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "valid IPv6 BmcIpAddress",
+ obj: APIExpectedMachineCreateRequest{
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ChassisSerialNumber: validChassisSerial,
+ BmcIpAddress: cdb.GetStrPtr("2001:db8::1"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "invalid BmcIpAddress",
+ obj: APIExpectedMachineCreateRequest{
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ChassisSerialNumber: validChassisSerial,
+ BmcIpAddress: cdb.GetStrPtr("not-an-ip"),
+ },
+ expectErr: true,
+ },
+ {
+ desc: "empty BmcIpAddress (pointer set, value empty)",
+ obj: APIExpectedMachineCreateRequest{
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ChassisSerialNumber: validChassisSerial,
+ BmcIpAddress: &emptyString,
+ },
+ expectErr: true,
+ },
+ {
+ desc: "nil BmcIpAddress (default)",
+ obj: APIExpectedMachineCreateRequest{
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ChassisSerialNumber: validChassisSerial,
+ BmcIpAddress: nil,
+ },
+ expectErr: false,
+ },
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
@@ -508,6 +564,47 @@ func TestAPIExpectedMachineUpdateRequest_Validate(t *testing.T) {
},
expectErr: true,
},
+ // BmcIpAddress validation tests
+ {
+ desc: "valid IPv4 BmcIpAddress",
+ obj: APIExpectedMachineUpdateRequest{
+ ChassisSerialNumber: &validChassisSerial,
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.10"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "valid IPv6 BmcIpAddress",
+ obj: APIExpectedMachineUpdateRequest{
+ ChassisSerialNumber: &validChassisSerial,
+ BmcIpAddress: cdb.GetStrPtr("2001:db8::1"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "invalid BmcIpAddress",
+ obj: APIExpectedMachineUpdateRequest{
+ ChassisSerialNumber: &validChassisSerial,
+ BmcIpAddress: cdb.GetStrPtr("not-an-ip"),
+ },
+ expectErr: true,
+ },
+ {
+ desc: "empty BmcIpAddress (pointer set, value empty)",
+ obj: APIExpectedMachineUpdateRequest{
+ ChassisSerialNumber: &validChassisSerial,
+ BmcIpAddress: &emptyString,
+ },
+ expectErr: true,
+ },
+ {
+ desc: "nil BmcIpAddress (default)",
+ obj: APIExpectedMachineUpdateRequest{
+ ChassisSerialNumber: &validChassisSerial,
+ BmcIpAddress: nil,
+ },
+ expectErr: false,
+ },
}
for _, tc := range tests {
diff --git a/api/pkg/api/model/expectedpowershelf.go b/api/pkg/api/model/expectedpowershelf.go
index 29bf08dd2..83994a5b7 100644
--- a/api/pkg/api/model/expectedpowershelf.go
+++ b/api/pkg/api/model/expectedpowershelf.go
@@ -42,8 +42,8 @@ type APIExpectedPowerShelfCreateRequest struct {
DefaultBmcPassword *string `json:"defaultBmcPassword"`
// ShelfSerialNumber is the serial number of the expected power shelf
ShelfSerialNumber string `json:"shelfSerialNumber"`
- // IpAddress is the IP address of the expected power shelf
- IpAddress *string `json:"ipAddress"`
+ // BmcIpAddress is the BMC IP address of the expected power shelf
+ BmcIpAddress *string `json:"bmcIpAddress"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
// Name is the optional name of the expected power shelf
@@ -85,6 +85,10 @@ func (epcr *APIExpectedPowerShelfCreateRequest) Validate() error {
validation.Length(1, 32).Error("Shelf serial number must be 32 characters or less")),
validation.Field(&epcr.RackID,
validation.NilOrNotEmpty.Error("RackID cannot be empty")),
+ validation.Field(&epcr.BmcIpAddress,
+ validation.NilOrNotEmpty.Error("BmcIpAddress cannot be empty"),
+ validation.When(epcr.BmcIpAddress != nil && *epcr.BmcIpAddress != "",
+ validationis.IP.Error("BmcIpAddress must be a valid IPv4 or IPv6 address"))),
validation.Field(&epcr.Name,
validation.NilOrNotEmpty.Error("Name cannot be empty")),
validation.Field(&epcr.Manufacturer,
@@ -120,8 +124,8 @@ type APIExpectedPowerShelfUpdateRequest struct {
DefaultBmcPassword *string `json:"defaultBmcPassword"`
// ShelfSerialNumber is the serial number of the expected power shelf
ShelfSerialNumber *string `json:"shelfSerialNumber"`
- // IpAddress is the IP address of the expected power shelf
- IpAddress *string `json:"ipAddress"`
+ // BmcIpAddress is the BMC IP address of the expected power shelf
+ BmcIpAddress *string `json:"bmcIpAddress"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
// Name is the optional name of the expected power shelf
@@ -177,6 +181,10 @@ func (epur *APIExpectedPowerShelfUpdateRequest) Validate() error {
validation.Length(1, 32).Error("Shelf Serial Number must be 1-32 characters")),
validation.Field(&epur.RackID,
validation.NilOrNotEmpty.Error("RackID cannot be empty")),
+ validation.Field(&epur.BmcIpAddress,
+ validation.NilOrNotEmpty.Error("BmcIpAddress cannot be empty"),
+ validation.When(epur.BmcIpAddress != nil && *epur.BmcIpAddress != "",
+ validationis.IP.Error("BmcIpAddress must be a valid IPv4 or IPv6 address"))),
validation.Field(&epur.Name,
validation.NilOrNotEmpty.Error("Name cannot be empty")),
validation.Field(&epur.Manufacturer,
@@ -212,8 +220,8 @@ type APIExpectedPowerShelf struct {
Site *APISite `json:"site,omitempty"`
// ShelfSerialNumber is the serial number of the expected power shelf
ShelfSerialNumber string `json:"shelfSerialNumber"`
- // IpAddress is the IP address of the expected power shelf
- IpAddress *string `json:"ipAddress"`
+ // BmcIpAddress is the BMC IP address of the expected power shelf
+ BmcIpAddress *string `json:"bmcIpAddress"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
// Name is the optional name of the expected power shelf
@@ -247,7 +255,7 @@ func NewAPIExpectedPowerShelf(dbModel *cdbm.ExpectedPowerShelf) *APIExpectedPowe
BmcMacAddress: dbModel.BmcMacAddress,
SiteID: dbModel.SiteID,
ShelfSerialNumber: dbModel.ShelfSerialNumber,
- IpAddress: dbModel.IpAddress,
+ BmcIpAddress: dbModel.BmcIpAddress,
RackID: dbModel.RackID,
Name: dbModel.Name,
Manufacturer: dbModel.Manufacturer,
diff --git a/api/pkg/api/model/expectedpowershelf_test.go b/api/pkg/api/model/expectedpowershelf_test.go
index 50bb38e11..a9972dd8c 100644
--- a/api/pkg/api/model/expectedpowershelf_test.go
+++ b/api/pkg/api/model/expectedpowershelf_test.go
@@ -34,7 +34,7 @@ func TestAPIExpectedPowerShelfCreateRequest_Validate(t *testing.T) {
validShelfSerial := "SHELF123"
validUsername := "admin"
validPassword := "password123"
- validIpAddress := "192.168.1.100"
+ validBmcIpAddress := "192.168.1.100"
tests := []struct {
desc string
@@ -60,7 +60,7 @@ func TestAPIExpectedPowerShelfCreateRequest_Validate(t *testing.T) {
DefaultBmcUsername: &validUsername,
DefaultBmcPassword: &validPassword,
ShelfSerialNumber: validShelfSerial,
- IpAddress: &validIpAddress,
+ BmcIpAddress: &validBmcIpAddress,
Labels: map[string]string{"env": "test", "zone": "us-west-1"},
},
expectErr: false,
@@ -225,6 +225,67 @@ func TestAPIExpectedPowerShelfCreateRequest_Validate(t *testing.T) {
},
expectErr: true,
},
+ // BmcIpAddress validation tests
+ {
+ desc: "valid IPv4 BmcIpAddress",
+ obj: APIExpectedPowerShelfCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ShelfSerialNumber: validShelfSerial,
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.10"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "valid IPv6 BmcIpAddress",
+ obj: APIExpectedPowerShelfCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ShelfSerialNumber: validShelfSerial,
+ BmcIpAddress: cdb.GetStrPtr("2001:db8::1"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "invalid BmcIpAddress",
+ obj: APIExpectedPowerShelfCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ShelfSerialNumber: validShelfSerial,
+ BmcIpAddress: cdb.GetStrPtr("not-an-ip"),
+ },
+ expectErr: true,
+ },
+ {
+ desc: "empty BmcIpAddress (pointer set, value empty)",
+ obj: APIExpectedPowerShelfCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ShelfSerialNumber: validShelfSerial,
+ BmcIpAddress: &emptyString,
+ },
+ expectErr: true,
+ },
+ {
+ desc: "nil BmcIpAddress (default)",
+ obj: APIExpectedPowerShelfCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ ShelfSerialNumber: validShelfSerial,
+ BmcIpAddress: nil,
+ },
+ expectErr: false,
+ },
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
@@ -238,11 +299,11 @@ func TestAPIExpectedPowerShelfCreateRequest_Validate(t *testing.T) {
}
func TestNewAPIExpectedPowerShelf(t *testing.T) {
- ipAddress := "192.168.1.100"
+ bmcIpAddress := "192.168.1.100"
dbEPS := &cdbm.ExpectedPowerShelf{
BmcMacAddress: "00:11:22:33:44:55",
ShelfSerialNumber: "SHELF123",
- IpAddress: &ipAddress,
+ BmcIpAddress: &bmcIpAddress,
Labels: map[string]string{"env": "test", "zone": "us-west-1"},
Created: cdb.GetCurTime(),
Updated: cdb.GetCurTime(),
@@ -266,7 +327,7 @@ func TestNewAPIExpectedPowerShelf(t *testing.T) {
// Note: BmcUsername and BmcPassword are not included as they're not stored in DB
assert.Equal(t, tc.dbObj.BmcMacAddress, got.BmcMacAddress)
assert.Equal(t, tc.dbObj.ShelfSerialNumber, got.ShelfSerialNumber)
- assert.Equal(t, tc.dbObj.IpAddress, got.IpAddress)
+ assert.Equal(t, tc.dbObj.BmcIpAddress, got.BmcIpAddress)
assert.Equal(t, tc.dbObj.Labels, got.Labels)
assert.Equal(t, tc.dbObj.Created, got.Created)
assert.Equal(t, tc.dbObj.Updated, got.Updated)
@@ -278,7 +339,7 @@ func TestNewAPIExpectedPowerShelfWithNilFields(t *testing.T) {
dbEPS := &cdbm.ExpectedPowerShelf{
BmcMacAddress: "00:11:22:33:44:55",
ShelfSerialNumber: "SHELF123",
- IpAddress: nil,
+ BmcIpAddress: nil,
Labels: nil,
Created: time.Now(),
Updated: time.Now(),
@@ -289,7 +350,7 @@ func TestNewAPIExpectedPowerShelfWithNilFields(t *testing.T) {
// Verify fields are properly handled when empty or nil
assert.Equal(t, dbEPS.BmcMacAddress, got.BmcMacAddress)
assert.Equal(t, dbEPS.ShelfSerialNumber, got.ShelfSerialNumber)
- assert.Nil(t, got.IpAddress)
+ assert.Nil(t, got.BmcIpAddress)
assert.Nil(t, got.Labels)
}
@@ -308,7 +369,7 @@ func TestAPIExpectedPowerShelfUpdateRequest_Validate(t *testing.T) {
desc: "ok when all fields are provided",
obj: APIExpectedPowerShelfUpdateRequest{
ShelfSerialNumber: &validShelfSerial,
- IpAddress: cdb.GetStrPtr("192.168.1.100"),
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.100"),
Labels: map[string]string{"env": "production", "zone": "us-east-1"},
},
expectErr: false,
@@ -332,7 +393,7 @@ func TestAPIExpectedPowerShelfUpdateRequest_Validate(t *testing.T) {
desc: "ok with nil values for all optional fields",
obj: APIExpectedPowerShelfUpdateRequest{
ShelfSerialNumber: nil,
- IpAddress: nil,
+ BmcIpAddress: nil,
DefaultBmcUsername: nil,
DefaultBmcPassword: nil,
Labels: nil,
@@ -438,6 +499,47 @@ func TestAPIExpectedPowerShelfUpdateRequest_Validate(t *testing.T) {
},
expectErr: true,
},
+ // BmcIpAddress validation tests
+ {
+ desc: "valid IPv4 BmcIpAddress",
+ obj: APIExpectedPowerShelfUpdateRequest{
+ ShelfSerialNumber: &validShelfSerial,
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.10"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "valid IPv6 BmcIpAddress",
+ obj: APIExpectedPowerShelfUpdateRequest{
+ ShelfSerialNumber: &validShelfSerial,
+ BmcIpAddress: cdb.GetStrPtr("2001:db8::1"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "invalid BmcIpAddress",
+ obj: APIExpectedPowerShelfUpdateRequest{
+ ShelfSerialNumber: &validShelfSerial,
+ BmcIpAddress: cdb.GetStrPtr("not-an-ip"),
+ },
+ expectErr: true,
+ },
+ {
+ desc: "empty BmcIpAddress (pointer set, value empty)",
+ obj: APIExpectedPowerShelfUpdateRequest{
+ ShelfSerialNumber: &validShelfSerial,
+ BmcIpAddress: &emptyString,
+ },
+ expectErr: true,
+ },
+ {
+ desc: "nil BmcIpAddress (default)",
+ obj: APIExpectedPowerShelfUpdateRequest{
+ ShelfSerialNumber: &validShelfSerial,
+ BmcIpAddress: nil,
+ },
+ expectErr: false,
+ },
}
for _, tc := range tests {
@@ -456,7 +558,7 @@ func TestNewAPIExpectedPowerShelfEdgeCases(t *testing.T) {
dbEPS := &cdbm.ExpectedPowerShelf{
BmcMacAddress: "",
ShelfSerialNumber: "",
- IpAddress: cdb.GetStrPtr(""),
+ BmcIpAddress: cdb.GetStrPtr(""),
Labels: map[string]string{"": ""},
Created: time.Now(),
Updated: time.Now(),
@@ -543,7 +645,7 @@ func TestNewAPIExpectedPowerShelfWithSite(t *testing.T) {
SiteID: siteID,
BmcMacAddress: "00:11:22:33:44:55",
ShelfSerialNumber: "SHELF123",
- IpAddress: cdb.GetStrPtr("192.168.1.100"),
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.100"),
Labels: map[string]string{},
Site: site,
Created: time.Now(),
diff --git a/api/pkg/api/model/expectedswitch.go b/api/pkg/api/model/expectedswitch.go
index 31819eca9..fa07c9366 100644
--- a/api/pkg/api/model/expectedswitch.go
+++ b/api/pkg/api/model/expectedswitch.go
@@ -48,6 +48,8 @@ type APIExpectedSwitchCreateRequest struct {
NvOsPassword *string `json:"nvOsPassword"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
+ // BmcIpAddress is the optional BMC IP address of the expected switch
+ BmcIpAddress *string `json:"bmcIpAddress"`
// Name is the optional name of the expected switch
Name *string `json:"name"`
// Manufacturer is the optional manufacturer of the expected switch
@@ -87,6 +89,10 @@ func (escr *APIExpectedSwitchCreateRequest) Validate() error {
validation.Length(1, 32).Error("Switch serial number must be 32 characters or less")),
validation.Field(&escr.RackID,
validation.NilOrNotEmpty.Error("RackID cannot be empty")),
+ validation.Field(&escr.BmcIpAddress,
+ validation.NilOrNotEmpty.Error("BmcIpAddress cannot be empty"),
+ validation.When(escr.BmcIpAddress != nil && *escr.BmcIpAddress != "",
+ validationis.IP.Error("BmcIpAddress must be a valid IPv4 or IPv6 address"))),
validation.Field(&escr.Name,
validation.NilOrNotEmpty.Error("Name cannot be empty")),
validation.Field(&escr.Manufacturer,
@@ -128,6 +134,8 @@ type APIExpectedSwitchUpdateRequest struct {
NvOsPassword *string `json:"nvOsPassword"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
+ // BmcIpAddress is the optional BMC IP address of the expected switch
+ BmcIpAddress *string `json:"bmcIpAddress"`
// Name is the optional name of the expected switch
Name *string `json:"name"`
// Manufacturer is the optional manufacturer of the expected switch
@@ -181,6 +189,10 @@ func (esur *APIExpectedSwitchUpdateRequest) Validate() error {
validation.Length(1, 32).Error("Switch Serial Number must be 1-32 characters")),
validation.Field(&esur.RackID,
validation.NilOrNotEmpty.Error("RackID cannot be empty")),
+ validation.Field(&esur.BmcIpAddress,
+ validation.NilOrNotEmpty.Error("BmcIpAddress cannot be empty"),
+ validation.When(esur.BmcIpAddress != nil && *esur.BmcIpAddress != "",
+ validationis.IP.Error("BmcIpAddress must be a valid IPv4 or IPv6 address"))),
validation.Field(&esur.Name,
validation.NilOrNotEmpty.Error("Name cannot be empty")),
validation.Field(&esur.Manufacturer,
@@ -218,6 +230,8 @@ type APIExpectedSwitch struct {
SwitchSerialNumber string `json:"switchSerialNumber"`
// RackID is the optional rack identifier
RackID *string `json:"rackId"`
+ // BmcIpAddress is the optional BMC IP address of the expected switch
+ BmcIpAddress *string `json:"bmcIpAddress"`
// Name is the optional name of the expected switch
Name *string `json:"name"`
// Manufacturer is the optional manufacturer of the expected switch
@@ -250,6 +264,7 @@ func NewAPIExpectedSwitch(dbModel *cdbm.ExpectedSwitch) *APIExpectedSwitch {
SiteID: dbModel.SiteID,
SwitchSerialNumber: dbModel.SwitchSerialNumber,
RackID: dbModel.RackID,
+ BmcIpAddress: dbModel.BmcIpAddress,
Name: dbModel.Name,
Manufacturer: dbModel.Manufacturer,
Model: dbModel.Model,
diff --git a/api/pkg/api/model/expectedswitch_test.go b/api/pkg/api/model/expectedswitch_test.go
index 3a29b4d09..a69d5f0e1 100644
--- a/api/pkg/api/model/expectedswitch_test.go
+++ b/api/pkg/api/model/expectedswitch_test.go
@@ -225,6 +225,67 @@ func TestAPIExpectedSwitchCreateRequest_Validate(t *testing.T) {
},
expectErr: true,
},
+ // BmcIpAddress validation tests
+ {
+ desc: "valid IPv4 BmcIpAddress",
+ obj: APIExpectedSwitchCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ SwitchSerialNumber: validSwitchSerial,
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.10"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "valid IPv6 BmcIpAddress",
+ obj: APIExpectedSwitchCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ SwitchSerialNumber: validSwitchSerial,
+ BmcIpAddress: cdb.GetStrPtr("2001:db8::1"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "invalid BmcIpAddress",
+ obj: APIExpectedSwitchCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ SwitchSerialNumber: validSwitchSerial,
+ BmcIpAddress: cdb.GetStrPtr("not-an-ip"),
+ },
+ expectErr: true,
+ },
+ {
+ desc: "empty BmcIpAddress (pointer set, value empty)",
+ obj: APIExpectedSwitchCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ SwitchSerialNumber: validSwitchSerial,
+ BmcIpAddress: &emptyString,
+ },
+ expectErr: true,
+ },
+ {
+ desc: "nil BmcIpAddress (default)",
+ obj: APIExpectedSwitchCreateRequest{
+ SiteID: "550e8400-e29b-41d4-a716-446655440000",
+ BmcMacAddress: "00:11:22:33:44:55",
+ DefaultBmcUsername: &validUsername,
+ DefaultBmcPassword: &validPassword,
+ SwitchSerialNumber: validSwitchSerial,
+ BmcIpAddress: nil,
+ },
+ expectErr: false,
+ },
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
@@ -238,9 +299,11 @@ func TestAPIExpectedSwitchCreateRequest_Validate(t *testing.T) {
}
func TestNewAPIExpectedSwitch(t *testing.T) {
+ bmcIP := "192.168.1.10"
dbES := &cdbm.ExpectedSwitch{
BmcMacAddress: "00:11:22:33:44:55",
SwitchSerialNumber: "SWITCH123",
+ BmcIpAddress: &bmcIP,
Labels: map[string]string{"env": "test", "zone": "us-west-1"},
Created: cdb.GetCurTime(),
Updated: cdb.GetCurTime(),
@@ -263,6 +326,7 @@ func TestNewAPIExpectedSwitch(t *testing.T) {
// Verify all fields are properly mapped
assert.Equal(t, tc.dbObj.BmcMacAddress, got.BmcMacAddress)
assert.Equal(t, tc.dbObj.SwitchSerialNumber, got.SwitchSerialNumber)
+ assert.Equal(t, tc.dbObj.BmcIpAddress, got.BmcIpAddress)
assert.Equal(t, tc.dbObj.Labels, got.Labels)
assert.Equal(t, tc.dbObj.Created, got.Created)
assert.Equal(t, tc.dbObj.Updated, got.Updated)
@@ -428,6 +492,47 @@ func TestAPIExpectedSwitchUpdateRequest_Validate(t *testing.T) {
},
expectErr: true,
},
+ // BmcIpAddress validation tests
+ {
+ desc: "valid IPv4 BmcIpAddress",
+ obj: APIExpectedSwitchUpdateRequest{
+ SwitchSerialNumber: &validSwitchSerial,
+ BmcIpAddress: cdb.GetStrPtr("192.168.1.10"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "valid IPv6 BmcIpAddress",
+ obj: APIExpectedSwitchUpdateRequest{
+ SwitchSerialNumber: &validSwitchSerial,
+ BmcIpAddress: cdb.GetStrPtr("2001:db8::1"),
+ },
+ expectErr: false,
+ },
+ {
+ desc: "invalid BmcIpAddress",
+ obj: APIExpectedSwitchUpdateRequest{
+ SwitchSerialNumber: &validSwitchSerial,
+ BmcIpAddress: cdb.GetStrPtr("not-an-ip"),
+ },
+ expectErr: true,
+ },
+ {
+ desc: "empty BmcIpAddress (pointer set, value empty)",
+ obj: APIExpectedSwitchUpdateRequest{
+ SwitchSerialNumber: &validSwitchSerial,
+ BmcIpAddress: &emptyString,
+ },
+ expectErr: true,
+ },
+ {
+ desc: "nil BmcIpAddress (default)",
+ obj: APIExpectedSwitchUpdateRequest{
+ SwitchSerialNumber: &validSwitchSerial,
+ BmcIpAddress: nil,
+ },
+ expectErr: false,
+ },
}
for _, tc := range tests {
diff --git a/db/pkg/db/model/expectedmachine.go b/db/pkg/db/model/expectedmachine.go
index 1f575f94c..ce34661b2 100644
--- a/db/pkg/db/model/expectedmachine.go
+++ b/db/pkg/db/model/expectedmachine.go
@@ -71,6 +71,7 @@ type ExpectedMachine struct {
MachineID *string `bun:"machine_id"`
Machine *Machine `bun:"rel:belongs-to,join:machine_id=id"`
FallbackDpuSerialNumbers []string `bun:"fallback_dpu_serial_numbers,array"`
+ BmcIpAddress *string `bun:"bmc_ip_address"`
RackID *string `bun:"rack_id"`
Name *string `bun:"name"`
Manufacturer *string `bun:"manufacturer"`
@@ -106,6 +107,9 @@ func (em *ExpectedMachine) ToProto(creds ExpectedMachineCredentials) *cwssaws.Ex
SkuId: em.SkuID,
}
+ if em.BmcIpAddress != nil {
+ proto.BmcIpAddress = em.BmcIpAddress
+ }
if em.RackID != nil {
proto.RackId = &cwssaws.RackId{Id: *em.RackID}
}
@@ -166,6 +170,7 @@ type ExpectedMachineCreateInput struct {
SkuID *string
MachineID *string
FallbackDpuSerialNumbers []string
+ BmcIpAddress *string
RackID *string
Name *string
Manufacturer *string
@@ -187,6 +192,7 @@ type ExpectedMachineUpdateInput struct {
SkuID *string
MachineID *string
FallbackDpuSerialNumbers []string
+ BmcIpAddress *string
RackID *string
Name *string
Manufacturer *string
@@ -205,6 +211,7 @@ type ExpectedMachineClearInput struct {
SkuID bool
MachineID bool
FallbackDpuSerialNumbers bool
+ BmcIpAddress bool
RackID bool
Name bool
Manufacturer bool
@@ -328,6 +335,7 @@ func (emsd ExpectedMachineSQLDAO) CreateMultiple(ctx context.Context, tx *db.Tx,
SkuID: input.SkuID,
MachineID: input.MachineID,
FallbackDpuSerialNumbers: input.FallbackDpuSerialNumbers,
+ BmcIpAddress: input.BmcIpAddress,
RackID: input.RackID,
Name: input.Name,
Manufacturer: input.Manufacturer,
@@ -599,6 +607,10 @@ func (emsd ExpectedMachineSQLDAO) UpdateMultiple(ctx context.Context, tx *db.Tx,
em.MachineID = input.MachineID
columnsSet["machine_id"] = true
}
+ if input.BmcIpAddress != nil {
+ em.BmcIpAddress = input.BmcIpAddress
+ columnsSet["bmc_ip_address"] = true
+ }
if input.RackID != nil {
em.RackID = input.RackID
columnsSet["rack_id"] = true
@@ -715,6 +727,10 @@ func (emsd ExpectedMachineSQLDAO) Clear(ctx context.Context, tx *db.Tx, input Ex
em.FallbackDpuSerialNumbers = nil
updatedFields = append(updatedFields, "fallback_dpu_serial_numbers")
}
+ if input.BmcIpAddress {
+ em.BmcIpAddress = nil
+ updatedFields = append(updatedFields, "bmc_ip_address")
+ }
if input.RackID {
em.RackID = nil
updatedFields = append(updatedFields, "rack_id")
diff --git a/db/pkg/db/model/expectedmachine_test.go b/db/pkg/db/model/expectedmachine_test.go
index 024bbb004..7e2854956 100644
--- a/db/pkg/db/model/expectedmachine_test.go
+++ b/db/pkg/db/model/expectedmachine_test.go
@@ -111,6 +111,7 @@ func TestExpectedMachineSQLDAO_Create(t *testing.T) {
BmcMacAddress: "00:1B:44:11:3A:B7",
ChassisSerialNumber: "CHASSIS123",
FallbackDpuSerialNumbers: []string{"DPU001", "DPU002"},
+ BmcIpAddress: db.GetStrPtr("192.168.1.10"),
Labels: map[string]string{
"environment": "test",
"location": "datacenter1",
@@ -191,6 +192,7 @@ func TestExpectedMachineSQLDAO_Create(t *testing.T) {
assert.Equal(t, input.BmcMacAddress, em.BmcMacAddress)
assert.Equal(t, input.ChassisSerialNumber, em.ChassisSerialNumber)
assert.Equal(t, input.FallbackDpuSerialNumbers, em.FallbackDpuSerialNumbers)
+ assert.Equal(t, input.BmcIpAddress, em.BmcIpAddress)
assert.Equal(t, input.Labels, em.Labels)
}
diff --git a/db/pkg/db/model/expectedpowershelf.go b/db/pkg/db/model/expectedpowershelf.go
index 088c8310a..bcd3f1b65 100644
--- a/db/pkg/db/model/expectedpowershelf.go
+++ b/db/pkg/db/model/expectedpowershelf.go
@@ -63,7 +63,7 @@ type ExpectedPowerShelf struct {
Site *Site `bun:"rel:belongs-to,join:site_id=id"`
BmcMacAddress string `bun:"bmc_mac_address,notnull"`
ShelfSerialNumber string `bun:"shelf_serial_number,notnull"`
- IpAddress *string `bun:"ip_address"`
+ BmcIpAddress *string `bun:"bmc_ip_address"`
RackID *string `bun:"rack_id"`
Name *string `bun:"name"`
Manufacturer *string `bun:"manufacturer"`
@@ -98,8 +98,8 @@ func (eps *ExpectedPowerShelf) ToProto(creds ExpectedPowerShelfCredentials) *cws
ShelfSerialNumber: eps.ShelfSerialNumber,
}
- if eps.IpAddress != nil {
- proto.BmcIpAddress = *eps.IpAddress
+ if eps.BmcIpAddress != nil {
+ proto.BmcIpAddress = *eps.BmcIpAddress
}
if eps.RackID != nil {
proto.RackId = &cwssaws.RackId{Id: *eps.RackID}
@@ -158,7 +158,7 @@ type ExpectedPowerShelfCreateInput struct {
SiteID uuid.UUID
BmcMacAddress string
ShelfSerialNumber string
- IpAddress *string
+ BmcIpAddress *string
RackID *string
Name *string
Manufacturer *string
@@ -177,7 +177,7 @@ type ExpectedPowerShelfUpdateInput struct {
ExpectedPowerShelfID uuid.UUID
BmcMacAddress *string
ShelfSerialNumber *string
- IpAddress *string
+ BmcIpAddress *string
RackID *string
Name *string
Manufacturer *string
@@ -193,7 +193,7 @@ type ExpectedPowerShelfUpdateInput struct {
// ExpectedPowerShelfClearInput input parameters for Clear method
type ExpectedPowerShelfClearInput struct {
ExpectedPowerShelfID uuid.UUID
- IpAddress bool
+ BmcIpAddress bool
RackID bool
Name bool
Manufacturer bool
@@ -278,7 +278,7 @@ func (epsd ExpectedPowerShelfSQLDAO) Create(ctx context.Context, tx *db.Tx, inpu
SiteID: input.SiteID,
BmcMacAddress: input.BmcMacAddress,
ShelfSerialNumber: input.ShelfSerialNumber,
- IpAddress: input.IpAddress,
+ BmcIpAddress: input.BmcIpAddress,
RackID: input.RackID,
Name: input.Name,
Manufacturer: input.Manufacturer,
@@ -380,10 +380,10 @@ func (epsd ExpectedPowerShelfSQLDAO) setQueryWithFilter(filter ExpectedPowerShel
if ok {
query = query.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.
- Where("to_tsvector('english', (coalesce(eps.bmc_mac_address, ' ') || ' ' || coalesce(eps.shelf_serial_number, ' ') || ' ' || coalesce(eps.ip_address, ' ') || ' ' || coalesce(eps.labels::text, ' '))) @@ to_tsquery('english', ?)", *searchTokens).
+ Where("to_tsvector('english', (coalesce(eps.bmc_mac_address, ' ') || ' ' || coalesce(eps.shelf_serial_number, ' ') || ' ' || coalesce(eps.bmc_ip_address, ' ') || ' ' || coalesce(eps.labels::text, ' '))) @@ to_tsquery('english', ?)", *searchTokens).
WhereOr("eps.bmc_mac_address ILIKE ?", "%"+searchQuery+"%").
WhereOr("eps.shelf_serial_number ILIKE ?", "%"+searchQuery+"%").
- WhereOr("eps.ip_address ILIKE ?", "%"+searchQuery+"%").
+ WhereOr("eps.bmc_ip_address ILIKE ?", "%"+searchQuery+"%").
WhereOr("eps.labels::text ILIKE ?", "%"+searchQuery+"%").
WhereOr("eps.id::text ILIKE ?", "%"+searchQuery+"%").
WhereOr("eps.site_id::text ILIKE ?", "%"+searchQuery+"%")
@@ -471,9 +471,9 @@ func (epsd ExpectedPowerShelfSQLDAO) Update(ctx context.Context, tx *db.Tx, inpu
eps.ShelfSerialNumber = *input.ShelfSerialNumber
columnsSet["shelf_serial_number"] = true
}
- if input.IpAddress != nil {
- eps.IpAddress = input.IpAddress
- columnsSet["ip_address"] = true
+ if input.BmcIpAddress != nil {
+ eps.BmcIpAddress = input.BmcIpAddress
+ columnsSet["bmc_ip_address"] = true
}
if input.RackID != nil {
eps.RackID = input.RackID
@@ -561,9 +561,9 @@ func (epsd ExpectedPowerShelfSQLDAO) Clear(ctx context.Context, tx *db.Tx, input
}
updatedFields := []string{}
- if input.IpAddress {
- eps.IpAddress = nil
- updatedFields = append(updatedFields, "ip_address")
+ if input.BmcIpAddress {
+ eps.BmcIpAddress = nil
+ updatedFields = append(updatedFields, "bmc_ip_address")
}
if input.RackID {
eps.RackID = nil
diff --git a/db/pkg/db/model/expectedpowershelf_test.go b/db/pkg/db/model/expectedpowershelf_test.go
index bb1bac841..3702ecd79 100644
--- a/db/pkg/db/model/expectedpowershelf_test.go
+++ b/db/pkg/db/model/expectedpowershelf_test.go
@@ -79,7 +79,7 @@ func TestExpectedPowerShelfSQLDAO_Create(t *testing.T) {
SiteID: site.ID,
BmcMacAddress: "00:1B:44:11:3A:B7",
ShelfSerialNumber: "SHELF123",
- IpAddress: db.GetStrPtr("192.168.1.100"),
+ BmcIpAddress: db.GetStrPtr("192.168.1.100"),
Labels: map[string]string{
"environment": "test",
"location": "datacenter1",
@@ -98,7 +98,7 @@ func TestExpectedPowerShelfSQLDAO_Create(t *testing.T) {
SiteID: site.ID,
BmcMacAddress: "00:1B:44:11:3A:B8",
ShelfSerialNumber: "SHELF789",
- IpAddress: db.GetStrPtr("10.0.0.1"),
+ BmcIpAddress: db.GetStrPtr("10.0.0.1"),
Labels: map[string]string{
"environment": "production",
},
@@ -109,7 +109,7 @@ func TestExpectedPowerShelfSQLDAO_Create(t *testing.T) {
SiteID: site.ID,
BmcMacAddress: "00:1B:44:11:3A:B9",
ShelfSerialNumber: "SHELF456",
- IpAddress: nil,
+ BmcIpAddress: nil,
Labels: nil,
CreatedBy: user.ID,
},
@@ -146,7 +146,7 @@ func TestExpectedPowerShelfSQLDAO_Create(t *testing.T) {
assert.NotNil(t, eps)
assert.Equal(t, input.BmcMacAddress, eps.BmcMacAddress)
assert.Equal(t, input.ShelfSerialNumber, eps.ShelfSerialNumber)
- assert.Equal(t, input.IpAddress, eps.IpAddress)
+ assert.Equal(t, input.BmcIpAddress, eps.BmcIpAddress)
assert.Equal(t, input.Labels, eps.Labels)
}
@@ -180,7 +180,7 @@ func testExpectedPowerShelfSQLDAOCreateExpectedPowerShelves(ctx context.Context,
SiteID: site.ID,
BmcMacAddress: "00:1B:44:11:3A:B7",
ShelfSerialNumber: "SHELF123",
- IpAddress: db.GetStrPtr("192.168.1.100"),
+ BmcIpAddress: db.GetStrPtr("192.168.1.100"),
Labels: map[string]string{
"environment": "test",
"location": "datacenter1",
@@ -194,7 +194,7 @@ func testExpectedPowerShelfSQLDAOCreateExpectedPowerShelves(ctx context.Context,
SiteID: site.ID,
BmcMacAddress: "00:1B:44:11:3A:B8",
ShelfSerialNumber: "SHELF789",
- IpAddress: db.GetStrPtr("10.0.0.1"),
+ BmcIpAddress: db.GetStrPtr("10.0.0.1"),
Labels: map[string]string{
"environment": "production",
},
@@ -207,7 +207,7 @@ func testExpectedPowerShelfSQLDAOCreateExpectedPowerShelves(ctx context.Context,
SiteID: site.ID,
BmcMacAddress: "00:1B:44:11:3A:B9",
ShelfSerialNumber: "SHELF456",
- IpAddress: nil,
+ BmcIpAddress: nil,
Labels: nil,
CreatedBy: user.ID,
})
@@ -275,7 +275,7 @@ func TestExpectedPowerShelfSQLDAO_GetByID(t *testing.T) {
assert.Equal(t, tc.eps.ID, tmp.ID)
assert.Equal(t, tc.eps.BmcMacAddress, tmp.BmcMacAddress)
assert.Equal(t, tc.eps.ShelfSerialNumber, tmp.ShelfSerialNumber)
- assert.Equal(t, tc.eps.IpAddress, tmp.IpAddress)
+ assert.Equal(t, tc.eps.BmcIpAddress, tmp.BmcIpAddress)
assert.Equal(t, tc.eps.Labels, tmp.Labels)
} else {
t.Logf("%s", err.Error())
@@ -495,7 +495,7 @@ func TestExpectedPowerShelfSQLDAO_Update(t *testing.T) {
desc: "Update IP address",
input: ExpectedPowerShelfUpdateInput{
ExpectedPowerShelfID: epsExp[2].ID,
- IpAddress: db.GetStrPtr("172.16.0.1"),
+ BmcIpAddress: db.GetStrPtr("172.16.0.1"),
},
expectedError: false,
},
@@ -527,8 +527,8 @@ func TestExpectedPowerShelfSQLDAO_Update(t *testing.T) {
if tc.input.ShelfSerialNumber != nil {
assert.Equal(t, *tc.input.ShelfSerialNumber, got.ShelfSerialNumber)
}
- if tc.input.IpAddress != nil {
- assert.Equal(t, tc.input.IpAddress, got.IpAddress)
+ if tc.input.BmcIpAddress != nil {
+ assert.Equal(t, tc.input.BmcIpAddress, got.BmcIpAddress)
}
if tc.input.Labels != nil {
assert.Equal(t, tc.input.Labels, got.Labels)
@@ -566,10 +566,10 @@ func TestExpectedPowerShelfSQLDAO_Clear(t *testing.T) {
verifyChildSpanner bool
}{
{
- desc: "can clear IpAddress",
+ desc: "can clear BmcIpAddress",
eps: epsExp[1],
input: ExpectedPowerShelfClearInput{
- IpAddress: true,
+ BmcIpAddress: true,
},
expectedUpdate: true,
},
@@ -585,8 +585,8 @@ func TestExpectedPowerShelfSQLDAO_Clear(t *testing.T) {
desc: "can clear multiple fields",
eps: epsExp[0],
input: ExpectedPowerShelfClearInput{
- IpAddress: true,
- Labels: true,
+ BmcIpAddress: true,
+ Labels: true,
},
expectedUpdate: true,
},
@@ -603,8 +603,8 @@ func TestExpectedPowerShelfSQLDAO_Clear(t *testing.T) {
tmp, err := epsd.Clear(ctx, nil, tc.input)
assert.Nil(t, err)
assert.NotNil(t, tmp)
- if tc.input.IpAddress {
- assert.Nil(t, tmp.IpAddress)
+ if tc.input.BmcIpAddress {
+ assert.Nil(t, tmp.BmcIpAddress)
}
if tc.input.Labels {
assert.Nil(t, tmp.Labels)
diff --git a/db/pkg/db/model/expectedswitch.go b/db/pkg/db/model/expectedswitch.go
index f71ff76fc..351f2a6cf 100644
--- a/db/pkg/db/model/expectedswitch.go
+++ b/db/pkg/db/model/expectedswitch.go
@@ -63,6 +63,7 @@ type ExpectedSwitch struct {
Site *Site `bun:"rel:belongs-to,join:site_id=id"`
BmcMacAddress string `bun:"bmc_mac_address,notnull"`
SwitchSerialNumber string `bun:"switch_serial_number,notnull"`
+ BmcIpAddress *string `bun:"bmc_ip_address"`
RackID *string `bun:"rack_id"`
Name *string `bun:"name"`
Manufacturer *string `bun:"manufacturer"`
@@ -98,6 +99,9 @@ func (es *ExpectedSwitch) ToProto(creds ExpectedSwitchCredentials) *cwssaws.Expe
SwitchSerialNumber: es.SwitchSerialNumber,
}
+ if es.BmcIpAddress != nil {
+ proto.BmcIpAddress = *es.BmcIpAddress
+ }
if es.RackID != nil {
proto.RackId = &cwssaws.RackId{Id: *es.RackID}
}
@@ -161,6 +165,7 @@ type ExpectedSwitchCreateInput struct {
SiteID uuid.UUID
BmcMacAddress string
SwitchSerialNumber string
+ BmcIpAddress *string
RackID *string
Name *string
Manufacturer *string
@@ -179,6 +184,7 @@ type ExpectedSwitchUpdateInput struct {
ExpectedSwitchID uuid.UUID
BmcMacAddress *string
SwitchSerialNumber *string
+ BmcIpAddress *string
RackID *string
Name *string
Manufacturer *string
@@ -194,6 +200,7 @@ type ExpectedSwitchUpdateInput struct {
// ExpectedSwitchClearInput input parameters for Clear method
type ExpectedSwitchClearInput struct {
ExpectedSwitchID uuid.UUID
+ BmcIpAddress bool
RackID bool
Name bool
Manufacturer bool
@@ -278,6 +285,7 @@ func (essd ExpectedSwitchSQLDAO) Create(ctx context.Context, tx *db.Tx, input Ex
SiteID: input.SiteID,
BmcMacAddress: input.BmcMacAddress,
SwitchSerialNumber: input.SwitchSerialNumber,
+ BmcIpAddress: input.BmcIpAddress,
RackID: input.RackID,
Name: input.Name,
Manufacturer: input.Manufacturer,
@@ -469,6 +477,10 @@ func (essd ExpectedSwitchSQLDAO) Update(ctx context.Context, tx *db.Tx, input Ex
es.SwitchSerialNumber = *input.SwitchSerialNumber
columnsSet["switch_serial_number"] = true
}
+ if input.BmcIpAddress != nil {
+ es.BmcIpAddress = input.BmcIpAddress
+ columnsSet["bmc_ip_address"] = true
+ }
if input.RackID != nil {
es.RackID = input.RackID
columnsSet["rack_id"] = true
@@ -555,6 +567,10 @@ func (essd ExpectedSwitchSQLDAO) Clear(ctx context.Context, tx *db.Tx, input Exp
}
updatedFields := []string{}
+ if input.BmcIpAddress {
+ es.BmcIpAddress = nil
+ updatedFields = append(updatedFields, "bmc_ip_address")
+ }
if input.RackID {
es.RackID = nil
updatedFields = append(updatedFields, "rack_id")
diff --git a/db/pkg/db/model/expectedswitch_test.go b/db/pkg/db/model/expectedswitch_test.go
index 29cc0b6b1..6d34ebf77 100644
--- a/db/pkg/db/model/expectedswitch_test.go
+++ b/db/pkg/db/model/expectedswitch_test.go
@@ -79,6 +79,7 @@ func TestExpectedSwitchSQLDAO_Create(t *testing.T) {
SiteID: site.ID,
BmcMacAddress: "00:1B:44:11:3A:B7",
SwitchSerialNumber: "SWITCH123",
+ BmcIpAddress: db.GetStrPtr("192.168.1.10"),
Labels: map[string]string{
"environment": "test",
"location": "datacenter1",
@@ -143,6 +144,7 @@ func TestExpectedSwitchSQLDAO_Create(t *testing.T) {
assert.NotNil(t, es)
assert.Equal(t, input.BmcMacAddress, es.BmcMacAddress)
assert.Equal(t, input.SwitchSerialNumber, es.SwitchSerialNumber)
+ assert.Equal(t, input.BmcIpAddress, es.BmcIpAddress)
assert.Equal(t, input.Labels, es.Labels)
}
diff --git a/db/pkg/migrations/20260430070805_expected_components_bmc_ip_address.go b/db/pkg/migrations/20260430070805_expected_components_bmc_ip_address.go
new file mode 100644
index 000000000..517e5c99c
--- /dev/null
+++ b/db/pkg/migrations/20260430070805_expected_components_bmc_ip_address.go
@@ -0,0 +1,79 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package migrations
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
+ // Start transaction
+ tx, terr := db.BeginTx(ctx, &sql.TxOptions{})
+ if terr != nil {
+ handlePanic(terr, "failed to begin transaction")
+ }
+
+ // Add bmc_ip_address column to expected_machine table
+ _, err := tx.Exec("ALTER TABLE expected_machine ADD COLUMN IF NOT EXISTS bmc_ip_address TEXT")
+ handleError(tx, err)
+
+ // Add bmc_ip_address column to expected_switch table
+ _, err = tx.Exec("ALTER TABLE expected_switch ADD COLUMN IF NOT EXISTS bmc_ip_address TEXT")
+ handleError(tx, err)
+
+ // Rename ip_address to bmc_ip_address on expected_power_shelf table.
+ // Guarded so it's idempotent + scoped to current_schema():
+ // - rename only when ip_address still exists, AND
+ // - bmc_ip_address does NOT yet exist (so a partial re-run is a no-op).
+ _, err = tx.Exec(`
+DO $$
+BEGIN
+ IF EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_schema = current_schema()
+ AND table_name = 'expected_power_shelf'
+ AND column_name = 'ip_address'
+ ) AND NOT EXISTS (
+ SELECT 1 FROM information_schema.columns
+ WHERE table_schema = current_schema()
+ AND table_name = 'expected_power_shelf'
+ AND column_name = 'bmc_ip_address'
+ ) THEN
+ ALTER TABLE expected_power_shelf RENAME COLUMN ip_address TO bmc_ip_address;
+ END IF;
+END $$;
+`)
+ handleError(tx, err)
+
+ terr = tx.Commit()
+ if terr != nil {
+ handlePanic(terr, "failed to commit transaction")
+ }
+
+ fmt.Print(" [up migration] Added bmc_ip_address column to 'expected_machine' and 'expected_switch'; renamed 'ip_address' to 'bmc_ip_address' on 'expected_power_shelf'. ")
+ return nil
+ }, func(ctx context.Context, db *bun.DB) error {
+ fmt.Print(" [down migration] No action taken")
+ return nil
+ })
+}
diff --git a/docs/index.html b/docs/index.html
index 693a5a89d..4e147eb4a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -49,14 +49,14 @@
data-styled.g14[id="sc-csCMJq"]{content:"jSIqAu,"}/*!sc*/
.cNCXIL{height:18px;width:18px;min-width:18px;vertical-align:middle;float:right;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/
.eiOVYa{height:1.3em;width:1.3em;min-width:1.3em;vertical-align:middle;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/
-.iivCUv{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;transition:transform 0.2s ease-out;transform:rotateZ(0);}/*!sc*/
-.iivCUv polygon{fill:#1d8127;}/*!sc*/
+.iZiZiV{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/
+.iZiZiV polygon{fill:#1d8127;}/*!sc*/
.kiMFkB{height:1.5em;width:1.5em;min-width:1.5em;vertical-align:middle;float:left;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/
.kiMFkB polygon{fill:#d41f1c;}/*!sc*/
.ivEQut{height:20px;width:20px;min-width:20px;vertical-align:middle;float:right;transition:transform 0.2s ease-out;transform:rotateZ(0);}/*!sc*/
.ivEQut polygon{fill:white;}/*!sc*/
.dTZWxX{height:18px;width:18px;min-width:18px;vertical-align:middle;transition:transform 0.2s ease-out;transform:rotateZ(-90deg);}/*!sc*/
-data-styled.g15[id="sc-fbJfz"]{content:"cNCXIL,eiOVYa,iivCUv,kiMFkB,ivEQut,dTZWxX,"}/*!sc*/
+data-styled.g15[id="sc-fbJfz"]{content:"cNCXIL,eiOVYa,iZiZiV,kiMFkB,ivEQut,dTZWxX,"}/*!sc*/
.dhobou{display:inline-block;padding:2px 8px;margin:0;background-color:#ffa500;color:#ffffff;font-size:13px;vertical-align:middle;line-height:1.6;border-radius:4px;font-weight:600;font-size:12px;}/*!sc*/
.dhobou +span[type]{margin-left:4px;}/*!sc*/
data-styled.g16[id="sc-brKeYM"]{content:"dhobou,"}/*!sc*/
@@ -401,10 +401,6 @@
.kwTqVQ::before{content:"—";font-weight:bold;width:1.5em;text-align:center;display:inline-block;vertical-align:top;}/*!sc*/
.kwTqVQ:focus{outline:0;}/*!sc*/
data-styled.g120[id="sc-caslwi"]{content:"brztng,fvWYOy,gQivdi,kwTqVQ,"}/*!sc*/
-.iODxiF{padding:10px;}/*!sc*/
-data-styled.g121[id="sc-hlindU"]{content:"iODxiF,"}/*!sc*/
-.HutVN{text-align:left;margin-top:1em;caption-side:top;}/*!sc*/
-data-styled.g122[id="sc-gWrJbx"]{content:"HutVN,"}/*!sc*/
.jJkGwY{vertical-align:top;}/*!sc*/
data-styled.g123[id="sc-fYaxgW"]{content:"jJkGwY,"}/*!sc*/
.hsJdXF{font-size:1.3em;padding:0.2em 0;margin:3em 0 1.1em;color:#333333;font-weight:normal;}/*!sc*/
@@ -630,15 +626,9 @@
Typical API Call Flow for Tenant
API service must be configured for Service Account access at the time of deployment. It cannot be enabled or disabled via API.
Responses