3286c7faaf
The customer sees the guest's current memory + allowed range on the Rendszer settings page and resizes it. The controller proxies + maps the agent's machine code to Hungarian; the agent (felhom-agent v0.90.0) enforces every bound and applies live (no reboot). agentapi: GuestMemory + ResizeMemory; ruled 412 -> *MemoryRefusedError (code+bounds); pre-0.90 agent 404 -> typed *StatusError. Capability: FeatureGuestMemoryResize + featureMinAgent 0.90.0 + a featureProbes row (type-asserts GuestMemory so the shared SupportProber/netAgent are untouched). UI (internal/web/system_memory_handlers.go, settings_system.html): "Szerver memoria (RAM)" card + number input; POST /api/system/memory/resize -> capability gate -> agent -> flash. JS confirm only on shrink. Code->Hungarian map; agent-outdated hides the control; agent-unreachable falls back to the guest's /proc/meminfo. Agent English never shown raw. Tests: agentapi (decode, 404, refusal-code, capability table) + web handler (success/ below_usage_floor/agent_outdated). Gates + go build/vet/test all pass.
113 lines
3.9 KiB
Go
113 lines
3.9 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/agentapi"
|
|
)
|
|
|
|
// fakeMemAgent is the memAgent seam for handler tests. AgentVersion drives the capability gate
|
|
// (version fast-path); NetVerifyStatus is the SupportProber stub.
|
|
type fakeMemAgent struct {
|
|
info agentapi.GuestMemoryInfo
|
|
resizeRes agentapi.MemoryResizeResult
|
|
resizeErr error
|
|
ver string
|
|
calls int
|
|
}
|
|
|
|
func (f *fakeMemAgent) GuestMemory(context.Context) (agentapi.GuestMemoryInfo, error) {
|
|
return f.info, nil
|
|
}
|
|
func (f *fakeMemAgent) ResizeMemory(_ context.Context, _ int64) (agentapi.MemoryResizeResult, error) {
|
|
f.calls++
|
|
return f.resizeRes, f.resizeErr
|
|
}
|
|
func (f *fakeMemAgent) NetVerifyStatus(context.Context) (agentapi.NetVerifyStatus, error) {
|
|
return agentapi.NetVerifyStatus{Phase: "none"}, nil
|
|
}
|
|
func (f *fakeMemAgent) AgentVersion() string { return f.ver }
|
|
|
|
func postResize(t *testing.T, s *Server, body string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
r := httptest.NewRequest(http.MethodPost, "/api/system/memory/resize", strings.NewReader(body))
|
|
w := httptest.NewRecorder()
|
|
s.ServeSystemAPI(w, r)
|
|
return w
|
|
}
|
|
|
|
// Success → 200 with the Hungarian old→new message.
|
|
func TestMemoryResize_Success(t *testing.T) {
|
|
s := testServer(t)
|
|
agent := &fakeMemAgent{ver: "0.90.0", resizeRes: agentapi.MemoryResizeResult{OldMB: 8192, NewMB: 12288}}
|
|
s.memAgentFn = func() (memAgent, error) { return agent, nil }
|
|
|
|
w := postResize(t, s, `{"memory_mb":12288}`)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("resize = %d (%s), want 200", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "A memória átméretezése megtörtént: 8192 MB → 12288 MB.") {
|
|
t.Errorf("success message missing: %s", w.Body.String())
|
|
}
|
|
if agent.calls != 1 {
|
|
t.Errorf("ResizeMemory calls = %d, want 1", agent.calls)
|
|
}
|
|
}
|
|
|
|
// below_usage_floor refusal → 412, the mapped Hungarian message, and the machine code (agent English
|
|
// never shown raw).
|
|
func TestMemoryResize_BelowUsageFloor(t *testing.T) {
|
|
s := testServer(t)
|
|
agent := &fakeMemAgent{
|
|
ver: "0.90.0",
|
|
resizeErr: &agentapi.MemoryRefusedError{
|
|
Code: "below_usage_floor",
|
|
Bounds: agentapi.GuestMemoryInfo{UsageMB: 3000, FloorMB: 3512, MinMB: 2048, MaxMB: 14336},
|
|
Msg: "requested 3300 MB is too close to current usage 3000 MB (floor 3512 MB)",
|
|
},
|
|
}
|
|
s.memAgentFn = func() (memAgent, error) { return agent, nil }
|
|
|
|
w := postResize(t, s, `{"memory_mb":3300}`)
|
|
if w.Code != http.StatusPreconditionFailed {
|
|
t.Fatalf("below_usage_floor = %d (%s), want 412", w.Code, w.Body.String())
|
|
}
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, `"code":"below_usage_floor"`) {
|
|
t.Errorf("machine code missing: %s", body)
|
|
}
|
|
if !strings.Contains(body, "túl közel van a jelenlegi felhasználáshoz (3000 MB)") {
|
|
t.Errorf("mapped Hungarian message missing: %s", body)
|
|
}
|
|
// The agent's raw English must NEVER surface.
|
|
if strings.Contains(body, "requested 3300 MB is too close") {
|
|
t.Errorf("agent English leaked to the customer: %s", body)
|
|
}
|
|
}
|
|
|
|
// A pre-0.90 agent (version < MinAgent) → the capability gate refuses up front with agent_outdated,
|
|
// and ResizeMemory is NEVER called.
|
|
func TestMemoryResize_AgentOutdated(t *testing.T) {
|
|
s := testServer(t)
|
|
agent := &fakeMemAgent{ver: "0.89.0"}
|
|
s.memAgentFn = func() (memAgent, error) { return agent, nil }
|
|
|
|
w := postResize(t, s, `{"memory_mb":12288}`)
|
|
if w.Code != http.StatusPreconditionFailed {
|
|
t.Fatalf("outdated agent = %d (%s), want 412", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), `"code":"agent_outdated"`) {
|
|
t.Errorf("agent_outdated code missing: %s", w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "rendszerfrissítése szükséges") {
|
|
t.Errorf("outdated Hungarian note missing: %s", w.Body.String())
|
|
}
|
|
if agent.calls != 0 {
|
|
t.Errorf("ResizeMemory called %d times on a gated request — must be 0", agent.calls)
|
|
}
|
|
}
|