Files
worker/internal/installer/sourceactivate_shell_test.go
Gleb Tv 674a7d82bf
Все проверки выполнены успешно
CI / test (push) Successful in 4m30s
Docker / Build and publish worker image (push) Successful in 17m26s
feat(installer): activate source builds atomically
2026-08-13 02:26:37 +03:00

439 строки
15 KiB
Go

package installer
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// This file exercises the rendered activation script against stub
// systemd/OpenRC implementations ("targeted shell fixture tests"). Real
// systemd and OpenRC cannot run inside the Docker/OpenSSH harness (the
// container PID 1 is sshd), so these tests execute the actual remote
// script on the host with fake `systemctl`, `rc-service`, and `rc-update`
// binaries that simulate an init-managed host. This covers the unit
// install/enable, the supervisor-driven restart, the `is-active`-based
// health loop, the enable-state rollback, the activation lock, and the
// interrupted-run recovery paths that the E2E fixtures cannot reach.
//
// Residual limitation: the stub init tools verify command flow and state
// transitions, not the real systemd/OpenRC unit semantics; a real
// init-system smoke test remains out of scope for the container harness.
// systemctlStub records every invocation and simulates unit state in a
// fake state directory: `is-active`/`is-enabled` consult marker files,
// `restart` marks the unit active (or fails when the fail-restart marker
// is present), and `enable`/`disable` toggle the enabled marker.
const systemctlStub = `#!/bin/sh
echo "$*" >> "$SYSCTL_LOG"
action="$1"
shift
unit=""
for a in "$@"; do
case "$a" in --*) continue ;; esac
unit="$a"
break
done
case "$action" in
is-active) [ -f "$FAKE_SYSTEMD_DIR/$unit.active" ] || exit 3 ;;
is-enabled) [ -f "$FAKE_SYSTEMD_DIR/$unit.enabled" ] || exit 1 ;;
enable) touch "$FAKE_SYSTEMD_DIR/$unit.enabled"; exit 0 ;;
disable) rm -f "$FAKE_SYSTEMD_DIR/$unit.enabled"; exit 0 ;;
daemon-reload) exit 0 ;;
restart) [ -f "$FAKE_SYSTEMD_DIR/fail-restart" ] && exit 1
touch "$FAKE_SYSTEMD_DIR/$unit.active"; exit 0 ;;
stop) rm -f "$FAKE_SYSTEMD_DIR/$unit.active"; exit 0 ;;
*) exit 0 ;;
esac
`
// rcServiceStub simulates OpenRC service state; rcUpdateStub simulates
// runlevel enablement.
const rcServiceStub = `#!/bin/sh
echo "$*" >> "$RC_LOG"
svc="$1"
action="$2"
case "$action" in
restart) [ -f "$FAKE_RC_DIR/fail-restart" ] && exit 1
touch "$FAKE_RC_DIR/$svc.active"; exit 0 ;;
status) [ -f "$FAKE_RC_DIR/$svc.active" ] || exit 1 ;;
stop) rm -f "$FAKE_RC_DIR/$svc.active"; exit 0 ;;
*) exit 0 ;;
esac
`
const rcUpdateStub = `#!/bin/sh
echo "$*" >> "$RCU_LOG"
case "$1" in
add) touch "$FAKE_RC_DIR/$2.enabled"; exit 0 ;;
del) rm -f "$FAKE_RC_DIR/$2.enabled"; exit 0 ;;
*) exit 0 ;;
esac
`
// workerStub stands in for the built worker binary: it satisfies the
// `--version` validation and the `/healthz` liveness probe.
const workerStub = `#!/bin/sh
if [ "$1" = "--version" ]; then
echo "rsmon-worker version=dev commit=deadbeefdead buildDate=2026-01-01T00:00:00Z"
exit 0
fi
if [ "$1" = "liveness" ]; then
exit 0
fi
exit 1
`
// initShellFixture builds a temp "host" with stub init tools and a stub
// worker, plus the uploaded env/unit the activation script consumes.
type initShellFixture struct {
root string
binDir string
sysdDir string
rcDir string
sysctlLog string
rcLog string
rcuLog string
params activateParams
}
func writeExec(t *testing.T, path, content string) {
t.Helper()
if err := os.WriteFile(path, []byte(content), 0o700); err != nil {
t.Fatal(err)
}
}
func newInitShellFixture(t *testing.T, supervisor string) *initShellFixture {
t.Helper()
root := t.TempDir()
fx := &initShellFixture{
root: root,
binDir: filepath.Join(root, "bin"),
sysdDir: filepath.Join(root, "sysd"),
rcDir: filepath.Join(root, "rc"),
sysctlLog: filepath.Join(root, "sysctl.log"),
rcLog: filepath.Join(root, "rc.log"),
rcuLog: filepath.Join(root, "rcu.log"),
}
for _, d := range []string{
fx.binDir, fx.sysdDir, fx.rcDir, filepath.Join(root, "upload"),
filepath.Join(root, "usr/local/bin"), filepath.Join(root, "etc/rsmon-worker"),
} {
if err := os.MkdirAll(d, 0o755); err != nil {
t.Fatal(err)
}
}
writeExec(t, filepath.Join(fx.binDir, "systemctl"), systemctlStub)
writeExec(t, filepath.Join(fx.binDir, "rc-service"), rcServiceStub)
writeExec(t, filepath.Join(fx.binDir, "rc-update"), rcUpdateStub)
stage := filepath.Join(root, "usr/local/bin/rsmon-worker.stage")
writeExec(t, stage, workerStub)
envTmp := filepath.Join(root, "upload", "worker.env")
unitTmp := filepath.Join(root, "upload", "unit")
if err := os.WriteFile(envTmp, []byte(
"RSMON_URL=https://rsmon.ru\nRSMON_TOKEN=shell-test-token\nWORKER_HOST=127.0.0.1\nWORKER_PORT=27401\n",
), 0o600); err != nil {
t.Fatal(err)
}
unitFile := filepath.Join(root, "etc/systemd/system/rsmon-worker.service")
unitMode := "0644"
if supervisor == "openrc" {
unitFile = filepath.Join(root, "etc/init.d/rsmon-worker")
unitMode = "0755"
if err := os.MkdirAll(filepath.Dir(unitFile), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(unitTmp, []byte("# fake openrc unit\n"), 0o755); err != nil {
t.Fatal(err)
}
} else {
if err := os.MkdirAll(filepath.Dir(unitFile), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(unitTmp, []byte("[Unit]\n# fake systemd unit\n"), 0o644); err != nil {
t.Fatal(err)
}
}
dataDir := filepath.Join(root, "var/lib/rsmon-worker")
fx.params = activateParams{
Stage: stage,
Binary: filepath.Join(root, "usr/local/bin/rsmon-worker"),
ConfigDir: filepath.Join(root, "etc/rsmon-worker"),
EnvFile: filepath.Join(root, "etc/rsmon-worker/worker.env"),
EnvTmp: envTmp,
DataDir: dataDir,
UnitTmp: unitTmp,
UnitFile: unitFile,
UnitMode: unitMode,
UnitName: "rsmon-worker.service",
RCName: "rsmon-worker",
Supervisor: supervisor,
NoStart: "0",
}
return fx
}
// run executes the rendered activation script with the stub init tools
// first in PATH.
func (fx *initShellFixture) run(t *testing.T) (string, error) {
t.Helper()
script := activateScript(fx.params)
cmd := exec.Command("sh", "-c", script)
cmd.Env = append(
os.Environ(),
"PATH="+fx.binDir+":"+os.Getenv("PATH"),
"FAKE_SYSTEMD_DIR="+fx.sysdDir,
"FAKE_RC_DIR="+fx.rcDir,
"SYSCTL_LOG="+fx.sysctlLog,
"RC_LOG="+fx.rcLog,
"RCU_LOG="+fx.rcuLog,
)
out, err := cmd.CombinedOutput()
return string(out), err
}
func (fx *initShellFixture) log(t *testing.T, path string) string {
t.Helper()
b, err := os.ReadFile(path)
if err != nil {
return ""
}
return string(b)
}
func (fx *initShellFixture) assertNoLeftovers(t *testing.T) {
t.Helper()
for _, name := range []string{".rsmon-backup", ".rsmon-activate.lock"} {
if _, err := os.Stat(filepath.Join(fx.params.DataDir, name)); !os.IsNotExist(err) {
t.Fatalf("%s left behind after activation", name)
}
}
}
// TestActivateShellSystemd runs the real activation script against a stub
// systemd host and verifies the init-managed flow: the unit is installed
// and enabled, restart goes through systemctl, and the health loop uses
// `systemctl is-active` (no pid file is ever created).
func TestActivateShellSystemd(t *testing.T) {
fx := newInitShellFixture(t, "systemd")
out, err := fx.run(t)
if err != nil {
t.Fatalf("systemd activation failed: %v\n%s", err, out)
}
if !strings.Contains(out, "rsmon-worker activated") {
t.Fatalf("no activation success line:\n%s", out)
}
unit, err := os.ReadFile(fx.params.UnitFile)
if err != nil {
t.Fatalf("unit not installed: %v", err)
}
if !strings.Contains(string(unit), "[Unit]") {
t.Fatalf("installed unit has wrong content: %q", unit)
}
st, err := os.Stat(fx.params.EnvFile)
if err != nil {
t.Fatalf("env not installed: %v", err)
}
if st.Mode().Perm() != 0o600 {
t.Fatalf("env mode = %v, want 0600", st.Mode().Perm())
}
log := fx.log(t, fx.sysctlLog)
for _, want := range []string{
"enable rsmon-worker.service",
"restart rsmon-worker.service",
"is-active --quiet rsmon-worker.service",
} {
if !strings.Contains(log, want) {
t.Fatalf("systemctl log missing %q:\n%s", want, log)
}
}
// The health loop used systemctl is-active, never the embedded
// supervisor's pid file.
if _, err := os.Stat(filepath.Join(fx.params.DataDir, "worker.pid")); !os.IsNotExist(err) {
t.Fatalf("systemd activation created a pid file; the health loop must use systemctl is-active")
}
if _, err := os.Stat(filepath.Join(fx.sysdDir, "rsmon-worker.service.enabled")); err != nil {
t.Fatalf("unit not enabled: %v", err)
}
fx.assertNoLeftovers(t)
}
// TestActivateShellOpenRC runs the real activation script against a stub
// OpenRC host: the init script is installed executable, the runlevel
// enablement and rc-service restart/status are used, and the health loop
// goes through rc-service (no pid file).
func TestActivateShellOpenRC(t *testing.T) {
fx := newInitShellFixture(t, "openrc")
out, err := fx.run(t)
if err != nil {
t.Fatalf("openrc activation failed: %v\n%s", err, out)
}
if !strings.Contains(out, "rsmon-worker activated") {
t.Fatalf("no activation success line:\n%s", out)
}
st, err := os.Stat(fx.params.UnitFile)
if err != nil {
t.Fatalf("openrc init not installed: %v", err)
}
if st.Mode().Perm() != 0o755 {
t.Fatalf("openrc init mode = %v, want 0755", st.Mode().Perm())
}
rcLog := fx.log(t, fx.rcLog)
for _, want := range []string{"rsmon-worker restart", "rsmon-worker status"} {
if !strings.Contains(rcLog, want) {
t.Fatalf("rc-service log missing %q:\n%s", want, rcLog)
}
}
rcuLog := fx.log(t, fx.rcuLog)
if !strings.Contains(rcuLog, "add rsmon-worker default") {
t.Fatalf("rc-update add not issued:\n%s", rcuLog)
}
if _, err := os.Stat(filepath.Join(fx.rcDir, "rsmon-worker.enabled")); err != nil {
t.Fatalf("service not enabled in the runlevel: %v", err)
}
if _, err := os.Stat(filepath.Join(fx.params.DataDir, "worker.pid")); !os.IsNotExist(err) {
t.Fatalf("openrc activation created a pid file; the health loop must use rc-service status")
}
fx.assertNoLeftovers(t)
}
// TestActivateShellSystemdRollbackFreshDisablesUnit proves a failed
// activation on a fresh host disables the newly-enabled unit and removes
// the unit file (no dangling enablement).
func TestActivateShellSystemdRollbackFreshDisablesUnit(t *testing.T) {
fx := newInitShellFixture(t, "systemd")
if err := os.WriteFile(filepath.Join(fx.sysdDir, "fail-restart"), []byte(""), 0o644); err != nil {
t.Fatal(err)
}
out, err := fx.run(t)
if err == nil {
t.Fatal("activation succeeded despite restart failure")
}
if !strings.Contains(out, "start failure") {
t.Fatalf("no start-failure classification:\n%s", out)
}
log := fx.log(t, fx.sysctlLog)
if !strings.Contains(log, "disable rsmon-worker.service") {
t.Fatalf("fresh rollback did not disable the unit:\n%s", log)
}
if _, err := os.Stat(fx.params.UnitFile); !os.IsNotExist(err) {
t.Fatal("unit file left behind after fresh rollback")
}
if _, err := os.Stat(filepath.Join(fx.sysdDir, "rsmon-worker.service.enabled")); !os.IsNotExist(err) {
t.Fatal("unit still enabled after fresh rollback")
}
fx.assertNoLeftovers(t)
}
// TestActivateShellSystemdRollbackRestoresEnableState proves a failed
// activation on a host with a prior enabled unit restores the unit
// byte-for-byte and re-applies the prior enabled state.
func TestActivateShellSystemdRollbackRestoresEnableState(t *testing.T) {
fx := newInitShellFixture(t, "systemd")
if err := os.WriteFile(fx.params.UnitFile, []byte("PRIOR-UNIT-CONTENT"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(fx.sysdDir, "rsmon-worker.service.enabled"), []byte(""), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(fx.sysdDir, "fail-restart"), []byte(""), 0o644); err != nil {
t.Fatal(err)
}
out, err := fx.run(t)
if err == nil {
t.Fatal("activation succeeded despite restart failure")
}
if !strings.Contains(out, "start failure") {
t.Fatalf("no start-failure classification:\n%s", out)
}
unit, err := os.ReadFile(fx.params.UnitFile)
if err != nil {
t.Fatal(err)
}
if string(unit) != "PRIOR-UNIT-CONTENT" {
t.Fatalf("prior unit not restored: %q", unit)
}
log := fx.log(t, fx.sysctlLog)
if !strings.Contains(log, "enable rsmon-worker.service") {
t.Fatalf("prior enabled state not re-applied:\n%s", log)
}
if _, err := os.Stat(filepath.Join(fx.sysdDir, "rsmon-worker.service.enabled")); err != nil {
t.Fatalf("unit not re-enabled after rollback: %v", err)
}
fx.assertNoLeftovers(t)
}
// TestActivateShellStaleLockBroken proves a leftover lock from a killed
// run (dead pid) is broken automatically and the activation proceeds.
func TestActivateShellStaleLockBroken(t *testing.T) {
fx := newInitShellFixture(t, "systemd")
lockDir := filepath.Join(fx.params.DataDir, ".rsmon-activate.lock")
if err := os.MkdirAll(lockDir, 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(lockDir, "pid"), []byte("999999\n"), 0o644); err != nil {
t.Fatal(err)
}
out, err := fx.run(t)
if err != nil {
t.Fatalf("activation failed with a stale lock: %v\n%s", err, out)
}
if !strings.Contains(out, "rsmon-worker activated") {
t.Fatalf("no activation success line:\n%s", out)
}
if _, err := os.Stat(lockDir); !os.IsNotExist(err) {
t.Fatal("lock not released after activation")
}
}
// TestActivateShellRecoveryFromInterruptedBackup proves the interrupted-run
// recovery: a leftover backup marker from a killed activation is restored
// before the fresh run, so a later validation failure still leaves the
// recovered prior install in place.
func TestActivateShellRecoveryFromInterruptedBackup(t *testing.T) {
fx := newInitShellFixture(t, "none")
backupDir := filepath.Join(fx.params.DataDir, ".rsmon-backup")
if err := os.MkdirAll(backupDir, 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(backupDir, "binary"), []byte("RECOVERED-BINARY"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(backupDir, "worker.env"), []byte("RSMON_URL=https://rsmon.ru\nRSMON_TOKEN=test\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(backupDir, ".marker"), []byte("0"), 0o600); err != nil {
t.Fatal(err)
}
// Corrupt the stage so the fresh activation fails at validation (before
// the fresh snapshot), proving the recovered files were already in place.
if err := os.WriteFile(fx.params.Stage, []byte("not a script"), 0o644); err != nil {
t.Fatal(err)
}
out, err := fx.run(t)
if err == nil {
t.Fatal("activation succeeded with a corrupt stage")
}
if !strings.Contains(out, "recovering interrupted activation") {
t.Fatalf("recovery did not run:\n%s", out)
}
b, err := os.ReadFile(fx.params.Binary)
if err != nil {
t.Fatalf("recovered binary not restored: %v", err)
}
if string(b) != "RECOVERED-BINARY" {
t.Fatalf("recovered binary content = %q", b)
}
if _, err := os.Stat(backupDir); !os.IsNotExist(err) {
t.Fatal("backup not consumed by recovery")
}
}