mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-15 10:58:21 +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.
74 lines
2.2 KiB
Go
74 lines
2.2 KiB
Go
//go:build linux
|
|
|
|
package host
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/Tencent/WeKnora/internal/logger"
|
|
)
|
|
|
|
// cpuPeriod is the cgroup cpu.max period, in microseconds.
|
|
const cpuPeriod = 100000
|
|
|
|
// envCgroup names a cgroup v2 directory delegated to WeKnora. Each plugin
|
|
// process then runs in a child cgroup with memory.max and cpu.max set from
|
|
// its resources. Unset: memory is capped with an rlimit only and CPU is not
|
|
// capped.
|
|
const envCgroup = "WEKNORA_PLUGIN_CGROUP"
|
|
|
|
// applyLimits caps a started plugin process. Memory is capped with
|
|
// RLIMIT_DATA, which needs no privileges and covers the heap and anonymous
|
|
// mappings of Go, Python and Node; with a delegated cgroup, memory.max and
|
|
// cpu.max apply as well. Failures are logged: an unlimited plugin still
|
|
// runs. It returns a cleanup that removes the plugin's cgroup.
|
|
|
|
func applyLimits(pid int, id string, l limits) func() {
|
|
ctx := context.Background()
|
|
if l.memoryBytes > 0 {
|
|
lim := &unix.Rlimit{Cur: uint64(l.memoryBytes), Max: uint64(l.memoryBytes)}
|
|
if err := unix.Prlimit(pid, unix.RLIMIT_DATA, lim, nil); err != nil {
|
|
logger.Warnf(ctx, "[plugin] %s: cap memory: %v", id, err)
|
|
}
|
|
}
|
|
root := strings.TrimSpace(os.Getenv(envCgroup))
|
|
if root == "" || (l.memoryBytes == 0 && l.cpuMilli == 0) {
|
|
return func() {}
|
|
}
|
|
dir := filepath.Join(root, fmt.Sprintf("plugin-%d", pid))
|
|
if err := setupCgroup(dir, pid, l); err != nil {
|
|
logger.Warnf(ctx, "[plugin] %s: cgroup %s: %v", id, dir, err)
|
|
_ = os.Remove(dir)
|
|
return func() {}
|
|
}
|
|
return func() { _ = os.Remove(dir) }
|
|
}
|
|
|
|
func setupCgroup(dir string, pid int, l limits) error {
|
|
if err := os.Mkdir(dir, 0o755); err != nil && !os.IsExist(err) {
|
|
return err
|
|
}
|
|
write := func(file, value string) error {
|
|
return os.WriteFile(filepath.Join(dir, file), []byte(value), 0o644)
|
|
}
|
|
if l.memoryBytes > 0 {
|
|
if err := write("memory.max", strconv.FormatInt(l.memoryBytes, 10)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if l.cpuMilli > 0 {
|
|
quota := l.cpuMilli * cpuPeriod / 1000
|
|
if err := write("cpu.max", fmt.Sprintf("%d %d", quota, cpuPeriod)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return write("cgroup.procs", strconv.Itoa(pid))
|
|
}
|