From 491f8d498e241719ccb04434675e852759ac5848 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Mon, 15 Dec 2025 14:19:56 +0800 Subject: [PATCH 1/3] feat: SetLinkPreference API with timeout for WiFi port Signed-off-by: Cheah, Kit Hwa --- config/config.yml | 2 +- go.mod | 2 +- go.sum | 2 - .../controller/httpapi/v1/devicemanagement.go | 4 ++ .../controller/httpapi/v1/linkpreference.go | 31 +++++++++ internal/entity/dto/v1/linkpreference.go | 64 +++++++++++++++++++ internal/usecase/devices/interfaces.go | 2 + internal/usecase/devices/linkpreference.go | 41 ++++++++++++ internal/usecase/devices/wsman/interfaces.go | 1 + internal/usecase/devices/wsman/message.go | 53 +++++++++++++++ 10 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 internal/controller/httpapi/v1/linkpreference.go create mode 100644 internal/entity/dto/v1/linkpreference.go create mode 100644 internal/usecase/devices/linkpreference.go diff --git a/config/config.yml b/config/config.yml index 12dc921e1..a450acc67 100644 --- a/config/config.yml +++ b/config/config.yml @@ -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 diff --git a/go.mod b/go.mod index e908a9054..f8b85dbf0 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/device-management-toolkit/console go 1.25 -// replace github.com/device-management-toolkit/go-wsman-messages/v2 => ../go-wsman-messages +replace github.com/device-management-toolkit/go-wsman-messages/v2 => ../go-wsman-messages require ( github.com/Masterminds/squirrel v1.5.4 diff --git a/go.sum b/go.sum index daf616cbc..435b3e7f8 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/device-management-toolkit/go-wsman-messages/v2 v2.35.0 h1:J5bQ12tjspC9MT5FORsykBXPcnGDB2p4cZME9zeIE3U= -github.com/device-management-toolkit/go-wsman-messages/v2 v2.35.0/go.mod h1:qlR1/nrV6QPse0918YSJ2bjMY8VNkm6qnNU0ioqQEsQ= github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= diff --git a/internal/controller/httpapi/v1/devicemanagement.go b/internal/controller/httpapi/v1/devicemanagement.go index bd5f474b5..87014f392 100644 --- a/internal/controller/httpapi/v1/devicemanagement.go +++ b/internal/controller/httpapi/v1/devicemanagement.go @@ -62,5 +62,9 @@ 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) } } + diff --git a/internal/controller/httpapi/v1/linkpreference.go b/internal/controller/httpapi/v1/linkpreference.go new file mode 100644 index 000000000..5a9824e8c --- /dev/null +++ b/internal/controller/httpapi/v1/linkpreference.go @@ -0,0 +1,31 @@ +package v1 + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/device-management-toolkit/console/internal/entity/dto/v1" +) + +// setLinkPreference sets the link preference (ME or Host) on a device's WiFi port. +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") + ErrorResponse(c, err) + + return + } + + c.JSON(http.StatusOK, response) +} diff --git a/internal/entity/dto/v1/linkpreference.go b/internal/entity/dto/v1/linkpreference.go new file mode 100644 index 000000000..e9179f8f1 --- /dev/null +++ b/internal/entity/dto/v1/linkpreference.go @@ -0,0 +1,64 @@ +package dto + +// LinkPreferenceRequest represents the request to set link preference on a device. +type LinkPreferenceRequest struct { + LinkPreference int `json:"linkPreference" binding:"required,min=1,max=2"` // 1 for ME, 2 for HOST + Timeout int `json:"timeout" binding:"required,min=0"` // Timeout in seconds +} + +// LinkPreferenceResponse represents the response from setting link preference. +type LinkPreferenceResponse struct { + ReturnValue int `json:"returnValue"` + ReturnValueStr string `json:"returnValueStr"` +} + +// LinkPreference enumeration values +const ( + LinkPreferenceME = 1 // Management Engine + LinkPreferenceHost = 2 // Host +) + +// Return value constants for SetLinkPreference +const ( + ReturnValueSuccess = 0 + ReturnValueNotSupported = 1 + ReturnValueUnknownFailed = 2 + ReturnValueTimeout = 3 + ReturnValueFailed = 4 + ReturnValueInvalidParameter = 5 + ReturnValueInUse = 6 + ReturnValueTransitionStarted = 4096 + ReturnValueInvalidStateTransition = 4097 + ReturnValueTimeoutParameterNotSupport = 4098 + ReturnValueBusy = 4099 +) + +// GetReturnValueString returns a human-readable string for the return value. +func GetReturnValueString(returnValue int) string { + switch returnValue { + case ReturnValueSuccess: + return "SUCCESS" + case ReturnValueNotSupported: + return "NOT_SUPPORTED" + case ReturnValueUnknownFailed: + return "UNKNOWN_FAILED" + case ReturnValueTimeout: + return "TIMEOUT" + case ReturnValueFailed: + return "FAILED" + case ReturnValueInvalidParameter: + return "INVALID_PARAMETER" + case ReturnValueInUse: + return "IN_USE" + case ReturnValueTransitionStarted: + return "TRANSITION_STARTED" + case ReturnValueInvalidStateTransition: + return "INVALID_STATE_TRANSITION" + case ReturnValueTimeoutParameterNotSupport: + return "TIMEOUT_PARAMETER_NOT_SUPPORT" + case ReturnValueBusy: + return "BUSY" + default: + return "UNKNOWN" + } +} diff --git a/internal/usecase/devices/interfaces.go b/internal/usecase/devices/interfaces.go index c3441fd21..207df2508 100644 --- a/internal/usecase/devices/interfaces.go +++ b/internal/usecase/devices/interfaces.go @@ -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) } ) diff --git a/internal/usecase/devices/linkpreference.go b/internal/usecase/devices/linkpreference.go new file mode 100644 index 000000000..2b4df3f1e --- /dev/null +++ b/internal/usecase/devices/linkpreference.go @@ -0,0 +1,41 @@ +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 port. +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 < 0 { + return dto.LinkPreferenceResponse{}, ErrValidationUseCase.Wrap("SetLinkPreference", "validate timeout", "timeout must be non-negative") + } + + device, _ := uc.device.SetupWsmanClient(*item, false, true) + + returnValue, err := device.SetLinkPreference(req.LinkPreference, req.Timeout) + if err != nil { + return dto.LinkPreferenceResponse{}, err + } + + return dto.LinkPreferenceResponse{ + ReturnValue: returnValue, + ReturnValueStr: dto.GetReturnValueString(returnValue), + }, nil +} diff --git a/internal/usecase/devices/wsman/interfaces.go b/internal/usecase/devices/wsman/interfaces.go index cf345a66d..35295a012 100644 --- a/internal/usecase/devices/wsman/interfaces.go +++ b/internal/usecase/devices/wsman/interfaces.go @@ -71,4 +71,5 @@ type Management interface { GetIPSKVMRedirectionSettingData() (kvmredirection.Response, error) SetIPSKVMRedirectionSettingData(data *kvmredirection.KVMRedirectionSettingsRequest) (kvmredirection.Response, error) DeleteCertificate(instanceID string) error + SetLinkPreference(linkPreference, timeout int) (int, error) } diff --git a/internal/usecase/devices/wsman/message.go b/internal/usecase/devices/wsman/message.go index b104bfc34..63a363699 100644 --- a/internal/usecase/devices/wsman/message.go +++ b/internal/usecase/devices/wsman/message.go @@ -81,8 +81,20 @@ var ( // ErrCIRADeviceNotConnected is returned when a CIRA device is not connected or not found. ErrCIRADeviceNotConnected = errors.New("CIRA device not connected/not found") + // ErrWsmanMessage is used for wrapping wsman message errors. + ErrWsmanMessage = &wsmanError{} ) +type wsmanError struct{} + +func (e *wsmanError) Error() string { + return "wsman message error" +} + +func (e *wsmanError) Wrap(operation, context, message string) error { + return errors.New(operation + ": " + context + ": " + message) +} + type ConnectionEntry struct { WsmanMessages wsman.Messages IsCIRA bool @@ -1917,3 +1929,44 @@ func (c *ConnectionEntry) GetIPSKVMRedirectionSettingData() (kvmredirection.Resp func (c *ConnectionEntry) SetIPSKVMRedirectionSettingData(req *kvmredirection.KVMRedirectionSettingsRequest) (kvmredirection.Response, error) { return c.WsmanMessages.IPS.KVMRedirectionSettingData.Put(req) } + +// SetLinkPreference sets the link preference (ME or Host) on the WiFi port. +// linkPreference: 1 for ME, 2 for Host +// timeout: timeout in seconds +// Returns the return value from the AMT device or an error. +func (c *ConnectionEntry) SetLinkPreference(linkPreference, timeout int) (int, error) { + // Get all ethernet port settings to find WiFi port + enumResponse, err := c.WsmanMessages.AMT.EthernetPortSettings.Enumerate() + if err != nil { + return -1, err + } + + pullResponse, err := c.WsmanMessages.AMT.EthernetPortSettings.Pull(enumResponse.Body.EnumerateResponse.EnumerationContext) + if err != nil { + return -1, err + } + + // Find WiFi port (PhysicalConnectionType = 3) + var wifiInstanceID string + + for i := range pullResponse.Body.PullResponse.EthernetPortItems { + port := &pullResponse.Body.PullResponse.EthernetPortItems[i] + // PhysicalConnectionType: 3 = Wireless LAN + if port.PhysicalConnectionType == 3 { + wifiInstanceID = port.InstanceID + break + } + } + + if wifiInstanceID == "" { + return -1, ErrWsmanMessage.Wrap("SetLinkPreference", "find WiFi port", "no WiFi port found (PhysicalConnectionType=3)") + } + + // Call SetLinkPreference on the WiFi port + response, err := c.WsmanMessages.AMT.EthernetPortSettings.SetLinkPreference(linkPreference, timeout, wifiInstanceID) + if err != nil { + return -1, err + } + + return response.Body.SetLinkPreferenceResponse.ReturnValue, nil +} From e49a7ef6c126cfdf51e35e2a1b0409fbacfce4d3 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Tue, 16 Dec 2025 13:02:04 +0800 Subject: [PATCH 2/3] fix: http status codes for setLinkPreference cases Signed-off-by: Cheah, Kit Hwa --- internal/controller/httpapi/v1/linkpreference.go | 16 +++++++++++++++- internal/usecase/devices/wsman/message.go | 4 +++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/controller/httpapi/v1/linkpreference.go b/internal/controller/httpapi/v1/linkpreference.go index 5a9824e8c..15f131ecc 100644 --- a/internal/controller/httpapi/v1/linkpreference.go +++ b/internal/controller/httpapi/v1/linkpreference.go @@ -1,11 +1,13 @@ 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 port. @@ -22,10 +24,22 @@ func (r *deviceManagementRoutes) setLinkPreference(c *gin.Context) { response, err := r.d.SetLinkPreference(c.Request.Context(), guid, req) if err != nil { r.l.Error(err, "http - v1 - setLinkPreference") + // Map specific errors to HTTP status codes (matching MPS implementation) + if errors.Is(err, wsman.ErrNoWiFiPort) { + c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + return + } ErrorResponse(c, err) return } - c.JSON(http.StatusOK, response) + // Map AMT return value to HTTP status code (matching MPS implementation) + // MPS logic: null -> 404, -1 or non-zero -> 400, 0 -> 200 + httpStatus := http.StatusOK + if response.ReturnValue == -1 || response.ReturnValue != 0 { + httpStatus = http.StatusBadRequest + } + + c.JSON(httpStatus, response) } diff --git a/internal/usecase/devices/wsman/message.go b/internal/usecase/devices/wsman/message.go index 63a363699..e13f5704d 100644 --- a/internal/usecase/devices/wsman/message.go +++ b/internal/usecase/devices/wsman/message.go @@ -83,6 +83,8 @@ var ( ErrCIRADeviceNotConnected = errors.New("CIRA device not connected/not found") // ErrWsmanMessage is used for wrapping wsman message errors. ErrWsmanMessage = &wsmanError{} + // ErrNoWiFiPort is returned when no WiFi port is found on the device. + ErrNoWiFiPort = errors.New("no WiFi port found (PhysicalConnectionType=3)") ) type wsmanError struct{} @@ -1959,7 +1961,7 @@ func (c *ConnectionEntry) SetLinkPreference(linkPreference, timeout int) (int, e } if wifiInstanceID == "" { - return -1, ErrWsmanMessage.Wrap("SetLinkPreference", "find WiFi port", "no WiFi port found (PhysicalConnectionType=3)") + return -1, ErrNoWiFiPort } // Call SetLinkPreference on the WiFi port From 24fcfb3816e0e923870a44f8de4f0f3d2e2166e7 Mon Sep 17 00:00:00 2001 From: "Cheah, Kit Hwa" Date: Wed, 17 Dec 2025 01:09:03 +0800 Subject: [PATCH 3/3] fix: clean up returnValues and error status Signed-off-by: Cheah, Kit Hwa --- .../controller/httpapi/v1/linkpreference.go | 24 +++++---- internal/entity/dto/v1/linkpreference.go | 49 ++----------------- internal/usecase/devices/linkpreference.go | 7 +-- 3 files changed, 20 insertions(+), 60 deletions(-) diff --git a/internal/controller/httpapi/v1/linkpreference.go b/internal/controller/httpapi/v1/linkpreference.go index 15f131ecc..23605ca02 100644 --- a/internal/controller/httpapi/v1/linkpreference.go +++ b/internal/controller/httpapi/v1/linkpreference.go @@ -22,24 +22,30 @@ func (r *deviceManagementRoutes) setLinkPreference(c *gin.Context) { } response, err := r.d.SetLinkPreference(c.Request.Context(), guid, req) + if err != nil { r.l.Error(err, "http - v1 - setLinkPreference") - // Map specific errors to HTTP status codes (matching MPS implementation) + // Handle no WiFi port error with 404 and error message if errors.Is(err, wsman.ErrNoWiFiPort) { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) + c.JSON(http.StatusNotFound, gin.H{ + "error": "Set Link Preference failed: No WiFi port found for guid : " + guid + ".", + }) 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 (matching MPS implementation) - // MPS logic: null -> 404, -1 or non-zero -> 400, 0 -> 200 - httpStatus := http.StatusOK - if response.ReturnValue == -1 || response.ReturnValue != 0 { - httpStatus = http.StatusBadRequest + // 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(httpStatus, response) + c.JSON(http.StatusOK, response) } diff --git a/internal/entity/dto/v1/linkpreference.go b/internal/entity/dto/v1/linkpreference.go index e9179f8f1..7a99bf2c2 100644 --- a/internal/entity/dto/v1/linkpreference.go +++ b/internal/entity/dto/v1/linkpreference.go @@ -8,8 +8,7 @@ type LinkPreferenceRequest struct { // LinkPreferenceResponse represents the response from setting link preference. type LinkPreferenceResponse struct { - ReturnValue int `json:"returnValue"` - ReturnValueStr string `json:"returnValueStr"` + ReturnValue int `json:"returnValue" example:"0"` // Return code. 0 indicates success } // LinkPreference enumeration values @@ -18,47 +17,5 @@ const ( LinkPreferenceHost = 2 // Host ) -// Return value constants for SetLinkPreference -const ( - ReturnValueSuccess = 0 - ReturnValueNotSupported = 1 - ReturnValueUnknownFailed = 2 - ReturnValueTimeout = 3 - ReturnValueFailed = 4 - ReturnValueInvalidParameter = 5 - ReturnValueInUse = 6 - ReturnValueTransitionStarted = 4096 - ReturnValueInvalidStateTransition = 4097 - ReturnValueTimeoutParameterNotSupport = 4098 - ReturnValueBusy = 4099 -) - -// GetReturnValueString returns a human-readable string for the return value. -func GetReturnValueString(returnValue int) string { - switch returnValue { - case ReturnValueSuccess: - return "SUCCESS" - case ReturnValueNotSupported: - return "NOT_SUPPORTED" - case ReturnValueUnknownFailed: - return "UNKNOWN_FAILED" - case ReturnValueTimeout: - return "TIMEOUT" - case ReturnValueFailed: - return "FAILED" - case ReturnValueInvalidParameter: - return "INVALID_PARAMETER" - case ReturnValueInUse: - return "IN_USE" - case ReturnValueTransitionStarted: - return "TRANSITION_STARTED" - case ReturnValueInvalidStateTransition: - return "INVALID_STATE_TRANSITION" - case ReturnValueTimeoutParameterNotSupport: - return "TIMEOUT_PARAMETER_NOT_SUPPORT" - case ReturnValueBusy: - return "BUSY" - default: - return "UNKNOWN" - } -} +// Console-specific return value for no WiFi port found +const ReturnValueNoWiFiPort = -1 diff --git a/internal/usecase/devices/linkpreference.go b/internal/usecase/devices/linkpreference.go index 2b4df3f1e..54a22fb7f 100644 --- a/internal/usecase/devices/linkpreference.go +++ b/internal/usecase/devices/linkpreference.go @@ -31,11 +31,8 @@ func (uc *UseCase) SetLinkPreference(c context.Context, guid string, req dto.Lin returnValue, err := device.SetLinkPreference(req.LinkPreference, req.Timeout) if err != nil { - return dto.LinkPreferenceResponse{}, err + return dto.LinkPreferenceResponse{ReturnValue: returnValue}, err } - return dto.LinkPreferenceResponse{ - ReturnValue: returnValue, - ReturnValueStr: dto.GetReturnValueString(returnValue), - }, nil + return dto.LinkPreferenceResponse{ReturnValue: returnValue}, nil }