Skip to content
Open
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
7,837 changes: 7,837 additions & 0 deletions cmd/app/doc/openapi.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ app:
name: console
repo: device-management-toolkit/console
version: DEVELOPMENT
encryption_key: ""
encryption_key: "base64encodedkey12345678901234567890123456789012"
allow_insecure_ciphers: false
http:
host: localhost
Expand Down
3 changes: 3 additions & 0 deletions internal/controller/httpapi/v1/devicemanagement.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,8 @@ func NewAmtRoutes(handler *gin.RouterGroup, d devices.Feature, amt amtexplorer.F
// KVM display settings
h.GET("kvm/displays/:guid", r.getKVMDisplays)
h.PUT("kvm/displays/:guid", r.setKVMDisplays)

// Network link preference
h.POST("network/linkPreference/:guid", r.setLinkPreference)
}
}
51 changes: 51 additions & 0 deletions internal/controller/httpapi/v1/linkpreference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package v1

import (
"errors"
"net/http"

"github.com/gin-gonic/gin"

"github.com/device-management-toolkit/console/internal/entity/dto/v1"
"github.com/device-management-toolkit/console/internal/usecase/devices/wsman"
)

// setLinkPreference sets the link preference (ME or Host) on a device's WiFi interface.
func (r *deviceManagementRoutes) setLinkPreference(c *gin.Context) {
guid := c.Param("guid")

var req dto.LinkPreferenceRequest
if err := c.ShouldBindJSON(&req); err != nil {
ErrorResponse(c, err)

return
}

response, err := r.d.SetLinkPreference(c.Request.Context(), guid, req)

if err != nil {
r.l.Error(err, "http - v1 - setLinkPreference")
// Handle no WiFi port error with 404 and error message
if errors.Is(err, wsman.ErrNoWiFiPort) {
c.JSON(http.StatusNotFound, gin.H{
"error": "Set Link Preference failed for guid: " + guid + ". - " + err.Error(),
})
return
}
// For other errors (device not found, validation, etc.), use standard error response
ErrorResponse(c, err)
return
}

// Map AMT return value to HTTP status code
// Non-zero return value -> 400 Bad Request with error message
// 0 -> 200 OK with success response
if response.ReturnValue != 0 {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Set Link Preference failed for guid: " + guid + ".",
})
return
}

c.JSON(http.StatusOK, response)
}
21 changes: 21 additions & 0 deletions internal/entity/dto/v1/linkpreference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dto

// LinkPreferenceRequest represents the request to set link preference on a device.
type LinkPreferenceRequest struct {
LinkPreference uint32 `json:"linkPreference" binding:"required,min=1,max=2"` // 1 for ME, 2 for HOST
Timeout uint32 `json:"timeout" binding:"required,min=0,max=65535"` // Timeout in seconds
}

// LinkPreferenceResponse represents the response from setting link preference.
type LinkPreferenceResponse struct {
ReturnValue int `json:"returnValue" example:"0"` // Return code. 0 indicates success, -1 for no WiFi interface
}

// LinkPreference enumeration values
const (
LinkPreferenceME = 1 // Management Engine
LinkPreferenceHost = 2 // Host
)

// Console-specific return value for no WiFi interface found
const ReturnValueNoWiFiPort = -1
15 changes: 15 additions & 0 deletions internal/mocks/devicemanagement_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions internal/mocks/wsman_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions internal/mocks/wsv1_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions internal/usecase/devices/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,7 @@ type (
// KVM Screen Settings (IPS_ScreenSettingData)
GetKVMScreenSettings(c context.Context, guid string) (dto.KVMScreenSettings, error)
SetKVMScreenSettings(c context.Context, guid string, req dto.KVMScreenSettingsRequest) (dto.KVMScreenSettings, error)
// Link Preference (AMT_EthernetPortSettings)
SetLinkPreference(c context.Context, guid string, req dto.LinkPreferenceRequest) (dto.LinkPreferenceResponse, error)
}
)
38 changes: 38 additions & 0 deletions internal/usecase/devices/linkpreference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package devices

import (
"context"

dto "github.com/device-management-toolkit/console/internal/entity/dto/v1"
)

// SetLinkPreference sets the link preference (ME or Host) on a device's WiFi interface.
func (uc *UseCase) SetLinkPreference(c context.Context, guid string, req dto.LinkPreferenceRequest) (dto.LinkPreferenceResponse, error) {
item, err := uc.repo.GetByID(c, guid, "")
if err != nil {
return dto.LinkPreferenceResponse{}, err
}

if item == nil || item.GUID == "" {
return dto.LinkPreferenceResponse{}, ErrNotFound
}

// Validate link preference value
if req.LinkPreference != dto.LinkPreferenceME && req.LinkPreference != dto.LinkPreferenceHost {
return dto.LinkPreferenceResponse{}, ErrValidationUseCase.Wrap("SetLinkPreference", "validate link preference", "linkPreference must be 1 (ME) or 2 (Host)")
}

// Validate timeout
if req.Timeout > 65535 {
return dto.LinkPreferenceResponse{}, ErrValidationUseCase.Wrap("SetLinkPreference", "validate timeout", "timeout max value is 65535")
}

device, _ := uc.device.SetupWsmanClient(*item, false, true)

returnValue, err := device.SetLinkPreference(req.LinkPreference, req.Timeout)
if err != nil {
return dto.LinkPreferenceResponse{ReturnValue: returnValue}, err
}

return dto.LinkPreferenceResponse{ReturnValue: returnValue}, nil
}
192 changes: 192 additions & 0 deletions internal/usecase/devices/linkpreference_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package devices_test

import (
"context"
"errors"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
gomock "go.uber.org/mock/gomock"

"github.com/device-management-toolkit/console/internal/entity"
"github.com/device-management-toolkit/console/internal/entity/dto/v1"
"github.com/device-management-toolkit/console/internal/mocks"
devices "github.com/device-management-toolkit/console/internal/usecase/devices"
"github.com/device-management-toolkit/console/internal/usecase/devices/wsman"
"github.com/device-management-toolkit/console/pkg/logger"
)

func initLinkPreferenceTest(t *testing.T) (*devices.UseCase, *mocks.MockWSMAN, *mocks.MockManagement, *mocks.MockDeviceManagementRepository) {
t.Helper()

mockCtl := gomock.NewController(t)
defer mockCtl.Finish()

repo := mocks.NewMockDeviceManagementRepository(mockCtl)
wsmanMock := mocks.NewMockWSMAN(mockCtl)
wsmanMock.EXPECT().Worker().Return().AnyTimes()

management := mocks.NewMockManagement(mockCtl)
log := logger.New("error")
u := devices.New(repo, wsmanMock, mocks.NewMockRedirection(mockCtl), log, mocks.MockCrypto{})

return u, wsmanMock, management, repo
}

func TestSetLinkPreference(t *testing.T) {
t.Parallel()

device := &entity.Device{
GUID: "device-guid-123",
TenantID: "tenant-id-456",
}

request := dto.LinkPreferenceRequest{
LinkPreference: 1, // ME
Timeout: 300,
}

tests := []struct {
name string
request dto.LinkPreferenceRequest
manMock func(*mocks.MockWSMAN, *mocks.MockManagement)
repoMock func(*mocks.MockDeviceManagementRepository)
res dto.LinkPreferenceResponse
err error
}{
{
name: "success - set to ME",
request: request,
manMock: func(man *mocks.MockWSMAN, man2 *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(wsman.Management(man2), nil)
man2.EXPECT().
SetLinkPreference(uint32(1), uint32(300)).
Return(0, nil)
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
res: dto.LinkPreferenceResponse{ReturnValue: 0},
err: nil,
},
{
name: "success - set to HOST",
request: dto.LinkPreferenceRequest{
LinkPreference: 2, // HOST
Timeout: 60,
},
manMock: func(man *mocks.MockWSMAN, man2 *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(wsman.Management(man2), nil)
man2.EXPECT().
SetLinkPreference(uint32(2), uint32(60)).
Return(0, nil)
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
res: dto.LinkPreferenceResponse{ReturnValue: 0},
err: nil,
},
{
name: "GetById fails - device not found",
request: request,
manMock: func(_ *mocks.MockWSMAN, _ *mocks.MockManagement) {},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(nil, ErrGeneral)
},
res: dto.LinkPreferenceResponse{},
err: devices.ErrGeneral,
},
{
name: "no WiFi port found",
request: request,
manMock: func(man *mocks.MockWSMAN, man2 *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(man2, nil)
man2.EXPECT().
SetLinkPreference(uint32(1), uint32(300)).
Return(-1, wsman.ErrNoWiFiPort)
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
res: dto.LinkPreferenceResponse{ReturnValue: -1},
err: wsman.ErrNoWiFiPort,
},
{
name: "SetLinkPreference fails with AMT error",
request: request,
manMock: func(man *mocks.MockWSMAN, man2 *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(man2, nil)
man2.EXPECT().
SetLinkPreference(uint32(1), uint32(300)).
Return(5, errors.New("invalid parameter"))
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
res: dto.LinkPreferenceResponse{ReturnValue: 5},
err: errors.New("invalid parameter"),
},
{
name: "SetLinkPreference fails with general error",
request: request,
manMock: func(man *mocks.MockWSMAN, man2 *mocks.MockManagement) {
man.EXPECT().
SetupWsmanClient(gomock.Any(), false, true).
Return(man2, nil)
man2.EXPECT().
SetLinkPreference(uint32(1), uint32(300)).
Return(0, ErrGeneral)
},
repoMock: func(repo *mocks.MockDeviceManagementRepository) {
repo.EXPECT().
GetByID(context.Background(), device.GUID, "").
Return(device, nil)
},
res: dto.LinkPreferenceResponse{ReturnValue: 0},
err: ErrGeneral,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

useCase, wsmanMock, management, repo := initLinkPreferenceTest(t)

if tc.manMock != nil {
tc.manMock(wsmanMock, management)
}

tc.repoMock(repo)

res, err := useCase.SetLinkPreference(context.Background(), device.GUID, tc.request)

require.Equal(t, tc.res, res)

if tc.err != nil {
assert.Equal(t, err.Error(), tc.err.Error())
}
})
}
}
Loading