mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-13 02:26:36 +08:00
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>
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user