Files
OpenFlare/internal/apps/agent/nginx/runtime_process_proc_test.go
T
Ryan 96a17ae35d fix(agent): recover OpenResty runtime after invalid PID (#45)
* fix(agent): recover OpenResty runtime after invalid PID

* fix(agent): scope OpenResty shutdown to instance processes

---------

Co-authored-by: Noru Wyrms <wyrmsnoru@gmail.com>
2026-10-08 08:39:40 +08:00

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)
}
}