mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-24 01:37:50 +08:00
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.
57 lines
1.5 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|