mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 18:26:36 +08:00
96a17ae35d
* fix(agent): recover OpenResty runtime after invalid PID * fix(agent): scope OpenResty shutdown to instance processes --------- Co-authored-by: Noru Wyrms <wyrmsnoru@gmail.com>
173 lines
6.0 KiB
Go
173 lines
6.0 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package nginx
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestInspectProcRuntime(t *testing.T) {
|
|
root := t.TempDir()
|
|
binary := filepath.Join(root, "openresty")
|
|
if err := os.WriteFile(binary, nil, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
executable, err := os.Stat(binary)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
config := filepath.Join(root, "nginx.conf")
|
|
writeProcFixture(t, root, 42, 1, 1000, "nginx: master process "+binary+" -c "+config, "S", binary)
|
|
// Root masters may run workers as a different user. Track them during shutdown.
|
|
writeProcFixture(t, root, 43, 42, 1001, "nginx: worker process", "S", binary)
|
|
writeProcFixture(t, root, 44, 42, 1000, "nginx: worker process", "Z", binary)
|
|
writeProcFixture(t, root, 45, 1, 1000, "unrelated command", "S", binary)
|
|
// Another instance using the same executable must not block this instance.
|
|
writeProcFixture(t, root, 48, 1, 1000, "nginx: master process "+binary+" -c /other/nginx.conf", "S", binary)
|
|
writeProcFixture(t, root, 49, 48, 1000, "nginx: worker process", "S", binary)
|
|
otherBinary := filepath.Join(root, "other")
|
|
if err := os.WriteFile(otherBinary, nil, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeProcFixture(t, root, 46, 1, 1000, "nginx: worker process", "S", otherBinary)
|
|
processes, err := inspectProcRuntime(root, executable, binary, config, 1000)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
processes = runtimeDescendants(processes, runtimeMasterIdentities(processes))
|
|
if len(processes) != 2 {
|
|
t.Fatalf("inspected processes = %v, want live master and worker", processes)
|
|
}
|
|
if processes[0] != (runtimeProcess{PID: 42, ParentPID: 1, Master: true, StartTime: "142"}) {
|
|
t.Errorf("master identity = %v, want PID 42 and start time 142", processes[0])
|
|
}
|
|
if processes[1].PID != 43 || processes[1].Master {
|
|
t.Errorf("worker identity = %v, want worker 43", processes[1])
|
|
}
|
|
if err := os.RemoveAll(filepath.Join(root, "42")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
remaining, err := inspectProcRuntime(root, executable, binary, config, 1000)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if master, err := findRuntimeMaster(remaining); err != nil || master.PID != 0 {
|
|
t.Errorf("unattributed worker blocks cold start: master=%v err=%v", master, err)
|
|
}
|
|
writeProcFixture(t, root, 47, 1, 1000, "nginx: master process "+binary+" -c "+config, "S", otherBinary)
|
|
if _, err := inspectProcRuntime(root, executable, binary, config, 1000); err == nil {
|
|
t.Error("inspectProcRuntime(replaced executable) = nil, want refusal")
|
|
}
|
|
}
|
|
|
|
func TestInspectProcRuntimeIgnoresDifferentConfigOrOwner(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
uid int
|
|
config string
|
|
}{
|
|
{name: "different config", uid: 1000, config: "/other/nginx.conf"},
|
|
{name: "different owner", uid: 1001, config: "/data/nginx.conf"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
root := t.TempDir()
|
|
binary := filepath.Join(root, "openresty")
|
|
if err := os.WriteFile(binary, nil, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
executable, err := os.Stat(binary)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeProcFixture(t, root, 42, 1, tc.uid, "nginx: master process "+binary+" -c "+tc.config, "S", binary)
|
|
processes, err := inspectProcRuntime(root, executable, binary, "/data/nginx.conf", 1000)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if master, err := findRuntimeMaster(processes); err != nil || master.PID != 0 {
|
|
t.Errorf("unrelated master blocks cold start: master=%v err=%v", master, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func writeProcFixture(t *testing.T, root string, pid, parentPID, uid int, title, state, binary string) {
|
|
t.Helper()
|
|
dir := filepath.Join(root, strconv.Itoa(pid))
|
|
if err := os.Mkdir(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fields := make([]string, 20)
|
|
for i := range fields {
|
|
fields[i] = "0"
|
|
}
|
|
fields[0] = state
|
|
fields[procParentPIDIndex] = strconv.Itoa(parentPID)
|
|
fields[19] = strconv.Itoa(pid + 100)
|
|
for name, content := range map[string]string{
|
|
"status": fmt.Sprintf("Uid:\t%d\t%d\t%d\t%d\n", uid, uid, uid, uid),
|
|
"cmdline": title + "\x00",
|
|
"stat": fmt.Sprintf("%d (nginx worker) %s\n", pid, strings.Join(fields, " ")),
|
|
} {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := os.Symlink(binary, filepath.Join(dir, "exe")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestInspectProcRuntimeScopesUnreadableExecutables(t *testing.T) {
|
|
if os.Geteuid() == 0 {
|
|
t.Skip("root can read the permission-denied fixture")
|
|
}
|
|
root := t.TempDir()
|
|
binary := filepath.Join(root, "openresty")
|
|
if err := os.WriteFile(binary, nil, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
executable, err := os.Stat(binary)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
denied := filepath.Join(root, "denied")
|
|
if err := os.Mkdir(denied, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inaccessible := filepath.Join(denied, "exe")
|
|
if err := os.Symlink(binary, inaccessible); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
config := "/data/nginx.conf"
|
|
writeProcFixture(t, root, 42, 1, 1000, "nginx: master process "+binary+" -c "+config, "S", inaccessible)
|
|
writeProcFixture(t, root, 43, 42, 1001, "nginx: worker process", "S", inaccessible)
|
|
writeProcFixture(t, root, 50, 1, 1000, "nginx: master process "+binary+" -c /other/nginx.conf", "S", inaccessible)
|
|
writeProcFixture(t, root, 51, 50, 1001, "nginx: worker process", "S", inaccessible)
|
|
if err := os.Chmod(denied, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := os.Chmod(denied, 0o700); err != nil {
|
|
t.Error(err)
|
|
}
|
|
})
|
|
if _, err := os.Stat(filepath.Join(root, "51", "exe")); !os.IsPermission(err) {
|
|
t.Fatalf("fixture exe read = %v, want permission denied", err)
|
|
}
|
|
processes, err := inspectProcRuntime(root, executable, binary, config, 1000)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
processes = runtimeDescendants(processes, runtimeMasterIdentities(processes))
|
|
if len(processes) != 2 || processes[0].PID != 42 || processes[1].PID != 43 {
|
|
t.Fatalf("instance processes = %v, want own master and worker", processes)
|
|
}
|
|
}
|