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
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,58 @@
}
},
"response": []
},
{
"name": "Set Link Preference",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Status code is 404\", function () {\r",
" pm.response.to.have.status(404);\r",
"});\r",
"\r",
"pm.test(\"Device should not be found\", function () {\r",
" var jsonData = pm.response.json();\r",
" pm.expect(jsonData.error).to.eq(\"Error not found\")\r",
"});\r",
""
],
"type": "text/javascript",
"packages": {}
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"linkPreference\": 1,\r\n \"timeout\": 60\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{protocol}}://{{host}}/api/v1/amt/network/linkPreference/{{deviceId}}",
"protocol": "{{protocol}}",
"host": [
"{{host}}"
],
"path": [
"api",
"v1",
"amt",
"network",
"linkPreference",
"{{deviceId}}"
]
}
},
"response": []
}
]
},
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)
}
}
53 changes: 53 additions & 0 deletions internal/controller/httpapi/v1/linkpreference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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)
}
94 changes: 94 additions & 0 deletions internal/controller/httpapi/v1/linkpreference_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package v1

import (
"bytes"
"net/http"
"net/http/httptest"
"testing"

"github.com/gin-gonic/gin"
gomock "go.uber.org/mock/gomock"

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

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

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

devMock := mocks.NewMockDeviceManagementFeature(mockCtl)

gin.SetMode(gin.TestMode)

engine := gin.New()

handler := engine.Group("/api/v1")

// Use NewAmtRoutes to register the route
NewAmtRoutes(handler, devMock, nil, nil, logger.New("error"))

// Success case -> device.SetLinkPreference returns ReturnValue 0
devMock.EXPECT().
SetLinkPreference(gomock.Any(), "my-guid", dto.LinkPreferenceRequest{LinkPreference: 1, Timeout: 60}).
Return(dto.LinkPreferenceResponse{ReturnValue: 0}, nil)

body := `{"linkPreference":1,"timeout":60}`

req := httptest.NewRequest(http.MethodPost, "/api/v1/amt/network/linkPreference/my-guid", bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")

w := httptest.NewRecorder()

engine.ServeHTTP(w, req)

if w.Code != http.StatusOK {
t.Fatalf("expected 200 OK, got %d body=%s", w.Code, w.Body.String())
}

// AMT error case -> non-zero return value maps to 400
devMock = mocks.NewMockDeviceManagementFeature(mockCtl)
engine = gin.New()
handler = engine.Group("/api/v1")
NewAmtRoutes(handler, devMock, nil, nil, logger.New("error"))

devMock.EXPECT().
SetLinkPreference(gomock.Any(), "my-guid", dto.LinkPreferenceRequest{LinkPreference: 1, Timeout: 60}).
Return(dto.LinkPreferenceResponse{ReturnValue: 5}, nil)

req = httptest.NewRequest(http.MethodPost, "/api/v1/amt/network/linkPreference/my-guid", bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")

w = httptest.NewRecorder()

engine.ServeHTTP(w, req)

if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400 Bad Request, got %d body=%s", w.Code, w.Body.String())
}

// No WiFi port case -> SetLinkPreference returns ErrNoWiFiPort -> handler returns 404
devMock = mocks.NewMockDeviceManagementFeature(mockCtl)
engine = gin.New()
handler = engine.Group("/api/v1")
NewAmtRoutes(handler, devMock, nil, nil, logger.New("error"))

devMock.EXPECT().
SetLinkPreference(gomock.Any(), "my-guid", dto.LinkPreferenceRequest{LinkPreference: 1, Timeout: 60}).
Return(dto.LinkPreferenceResponse{ReturnValue: -1}, wsman.ErrNoWiFiPort)

req = httptest.NewRequest(http.MethodPost, "/api/v1/amt/network/linkPreference/my-guid", bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")

w = httptest.NewRecorder()

engine.ServeHTTP(w, req)

if w.Code != http.StatusNotFound {
t.Fatalf("expected 404 Not Found, got %d body=%s", w.Code, w.Body.String())
}
}
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)
}
)
40 changes: 40 additions & 0 deletions internal/usecase/devices/linkpreference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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
const maxTimeout = 65535

if req.Timeout > maxTimeout {
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
}
Loading