Files
WeKnora/internal/plugin/host/limits_linux_test.go
T
wizardchen d46fa25e41 feat(plugin): dynamic form options, OAuth connections and resource limits
Plugin config forms can now ask the plugin while they are filled in:

- x-options {name, dependsOn, search} loads a field's choices from
  /v1/options/{name}. POST /api/v1/plugins/:id/options merges the form's
  values with the stored secrets (system, tenant or instance scope) so a
  redacted token still works. Go and Python SDKs, OpenAPI and conformance
  cover the endpoint.
- x-oauth turns a string field into an account connection. WeKnora runs
  the authorization code flow (PKCE, client credentials from the plugin's
  system config), keeps the tokens sealed in plugin_oauth_connections and
  the field holds "oauth:<id>". The invoker swaps references for fresh
  access tokens at call time, refreshing near expiry. Callback:
  /api/v1/plugin-oauth/callback, one-time states in Redis or memory.
- SchemaForm gains an options widget (dependsOn reload, remote search)
  and an OAuth connect widget; connector, web search, workspace and
  platform config forms provide the plugin behind them.

Host plugins are capped per runtime.resources on Linux: RLIMIT_DATA for
memory (WEKNORA_PLUGIN_MEMORY_DEFAULT when undeclared) and, with a
delegated cgroup v2 (WEKNORA_PLUGIN_CGROUP), memory.max and cpu.max.
2026-09-26 11:51:35 +08:00

57 lines
1.5 KiB
Go

//go:build linux
package host
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestApplyLimitsCapsMemory(t *testing.T) {
cmd := exec.Command("sleep", "30")
if err := cmd.Start(); err != nil {
t.Skipf("no sleep: %v", err)
}
defer func() { _ = cmd.Process.Kill(); _ = cmd.Wait() }()
// A cgroup root we cannot write to is logged, not fatal.
t.Setenv(envCgroup, filepath.Join(t.TempDir(), "not-a-cgroup"))
release := applyLimits(cmd.Process.Pid, "acme.test", limits{memoryBytes: 64 << 20, cpuMilli: 500})
defer release()
raw, err := os.ReadFile(fmt.Sprintf("/proc/%d/limits", cmd.Process.Pid))
if err != nil {
t.Fatal(err)
}
for _, line := range strings.Split(string(raw), "\n") {
if strings.HasPrefix(line, "Max data size") {
if !strings.Contains(line, fmt.Sprint(64<<20)) {
t.Fatalf("limit not applied: %s", line)
}
return
}
}
t.Fatal("no data size limit line")
}
func TestSetupCgroupWritesLimits(t *testing.T) {
// A plain directory stands in for a delegated cgroup: the files are
// what the kernel would read.
dir := filepath.Join(t.TempDir(), "plugin-1")
if err := setupCgroup(dir, 4242, limits{memoryBytes: 1 << 30, cpuMilli: 1500}); err != nil {
t.Fatal(err)
}
for file, want := range map[string]string{
"memory.max": fmt.Sprint(1 << 30), "cpu.max": "150000 100000", "cgroup.procs": "4242",
} {
got, _ := os.ReadFile(filepath.Join(dir, file))
if string(got) != want {
t.Errorf("%s = %q, want %q", file, got, want)
}
}
}