Skip to content

Commit 5c82ee9

Browse files
committed
add afterlease hook cleanup in standalone mode
Also add hook tests for the standalone mode Signed-off-by: Benny Zlotnik <bzlotnik@redhat.com> Assisted-by: claude-opus-4.6
1 parent e911e4d commit 5c82ee9

4 files changed

Lines changed: 131 additions & 30 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: jumpstarter.dev/v1alpha1
2+
kind: ExporterConfig
3+
metadata:
4+
name: test-exporter-direct-hooks
5+
namespace: default
6+
export:
7+
power:
8+
type: jumpstarter_driver_power.driver.MockPower
9+
hooks:
10+
beforeLease:
11+
script: |
12+
echo "BEFORE_HOOK_DIRECT: executed"
13+
j power on
14+
echo "BEFORE_HOOK_DIRECT: complete"
15+
timeout: 60
16+
onFailure: warn
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: jumpstarter.dev/v1alpha1
2+
kind: ExporterConfig
3+
metadata:
4+
name: test-exporter-direct-hooks
5+
namespace: default
6+
export:
7+
power:
8+
type: jumpstarter_driver_power.driver.MockPower
9+
hooks:
10+
beforeLease:
11+
script: |
12+
echo "BEFORE_HOOK_DIRECT: executed"
13+
timeout: 60
14+
onFailure: warn
15+
afterLease:
16+
script: |
17+
echo "AFTER_HOOK_DIRECT: executed"
18+
timeout: 60
19+
onFailure: warn

e2e/tests-direct-listener.bats

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,59 @@ setup() {
1313
bats_require_minimum_version 1.5.0
1414
}
1515

16-
start_exporter() {
17-
jmp run --exporter-config "$EXPORTER_CONFIG" \
18-
--tls-grpc-listener "$LISTENER_PORT" \
19-
--tls-grpc-insecure &
16+
# Start the exporter in the background.
17+
# $1 - config file (default: $EXPORTER_CONFIG)
18+
# $2 - readiness: "grpc" waits via jmp shell (drains LogStream),
19+
# "port" waits via nc -z (preserves LogStream queue)
20+
# $3 - if set, redirect stderr to ${BATS_TEST_TMPDIR}/exporter.log
21+
_start_exporter() {
22+
local config="${1:-$EXPORTER_CONFIG}"
23+
local readiness="${2:-grpc}"
24+
local capture_logs="${3:-}"
25+
26+
if [ -n "$capture_logs" ]; then
27+
jmp run --exporter-config "$config" \
28+
--tls-grpc-listener "$LISTENER_PORT" \
29+
--tls-grpc-insecure 2>"${BATS_TEST_TMPDIR}/exporter.log" &
30+
else
31+
jmp run --exporter-config "$config" \
32+
--tls-grpc-listener "$LISTENER_PORT" \
33+
--tls-grpc-insecure &
34+
fi
2035
LISTENER_PID=$!
2136
echo "$LISTENER_PID" > "${BATS_TEST_TMPDIR}/exporter.pid"
2237

23-
# Wait for the gRPC server to be ready
2438
local retries=30
25-
while ! jmp shell --tls-grpc "127.0.0.1:${LISTENER_PORT}" --tls-grpc-insecure -- j --help >/dev/null 2>&1; do
26-
retries=$((retries - 1))
27-
if [ "$retries" -le 0 ]; then
28-
echo "Exporter did not become ready in time" >&2
29-
return 1
30-
fi
31-
sleep 0.5
32-
done
39+
if [ "$readiness" = "port" ]; then
40+
# TCP-only check: doesn't drain the LogStream queue, so hook output
41+
# remains buffered for the test command to consume.
42+
while ! nc -z 127.0.0.1 "$LISTENER_PORT" 2>/dev/null; do
43+
retries=$((retries - 1))
44+
if [ "$retries" -le 0 ]; then
45+
echo "Port $LISTENER_PORT did not become available" >&2
46+
return 1
47+
fi
48+
sleep 0.5
49+
done
50+
else
51+
# Full gRPC check: ensures exporter is ready for commands.
52+
# Drains LogStream queue (unsuitable for hook output tests).
53+
while ! jmp shell --tls-grpc "127.0.0.1:${LISTENER_PORT}" --tls-grpc-insecure -- j --help >/dev/null 2>&1; do
54+
retries=$((retries - 1))
55+
if [ "$retries" -le 0 ]; then
56+
echo "Exporter did not become ready in time" >&2
57+
return 1
58+
fi
59+
sleep 0.5
60+
done
61+
fi
3362
}
3463

64+
start_exporter() { _start_exporter "$1" grpc; }
65+
start_exporter_with_logs() { _start_exporter "$1" grpc logs; }
66+
start_exporter_bg() { _start_exporter "$1" port; }
67+
start_exporter_bg_with_logs() { _start_exporter "$1" port logs; }
68+
3569
stop_exporter() {
3670
if [ -f "${BATS_TEST_TMPDIR}/exporter.pid" ]; then
3771
local pid
@@ -71,3 +105,32 @@ teardown() {
71105
run jmp shell --tls-grpc "127.0.0.1:${LISTENER_PORT}" -- j power on
72106
assert_failure
73107
}
108+
109+
@test "direct listener hooks: beforeLease hook executes and j commands work" {
110+
# Use start_exporter_bg (TCP-only readiness check) to avoid draining
111+
# the LogStream queue before the test command connects.
112+
start_exporter_bg "${SCRIPT_DIR}/exporters/exporter-direct-hooks-before.yaml"
113+
114+
run jmp shell --tls-grpc "127.0.0.1:${LISTENER_PORT}" --tls-grpc-insecure \
115+
--exporter-logs -- j power off
116+
assert_success
117+
assert_output --partial "BEFORE_HOOK_DIRECT: executed"
118+
assert_output --partial "BEFORE_HOOK_DIRECT: complete"
119+
}
120+
121+
@test "direct listener hooks: afterLease hook runs on exporter shutdown" {
122+
start_exporter_bg_with_logs "${SCRIPT_DIR}/exporters/exporter-direct-hooks-both.yaml"
123+
124+
run jmp shell --tls-grpc "127.0.0.1:${LISTENER_PORT}" --tls-grpc-insecure \
125+
--exporter-logs -- j power on
126+
assert_success
127+
assert_output --partial "BEFORE_HOOK_DIRECT: executed"
128+
129+
# Stop the exporter (SIGTERM triggers _cleanup_after_lease).
130+
# stop_exporter waits for the process to exit, so the log is complete.
131+
stop_exporter
132+
133+
# afterLease hook output should appear in the exporter's stderr log
134+
run cat "${BATS_TEST_TMPDIR}/exporter.log"
135+
assert_output --partial "AFTER_HOOK_DIRECT: executed"
136+
}

python/packages/jumpstarter/jumpstarter/exporter/exporter.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ async def _cleanup_after_lease(self, lease_scope: LeaseContext) -> None:
591591
if not lease_scope.after_lease_hook_started.is_set():
592592
lease_scope.after_lease_hook_started.set()
593593
if (self.hook_executor
594-
and lease_scope.has_client()
594+
and (lease_scope.has_client() or self._standalone)
595595
and not lease_scope.skip_after_lease_hook):
596596
logger.info("Running afterLease hook on session close")
597597
await self.hook_executor.run_after_lease_hook(
@@ -856,22 +856,25 @@ async def serve_standalone_tcp(
856856
async with session.serve_tcp_and_unix_async(
857857
host, port, hook_path_str, tls_credentials=tls_credentials
858858
):
859-
async with create_task_group() as tg:
860-
self._tg = tg
861-
tg.start_soon(self._handle_end_session, lease_scope)
862-
863-
if self.hook_executor:
864-
await self.hook_executor.run_before_lease_hook(
865-
lease_scope,
866-
self._report_status,
867-
self.stop,
868-
self._request_lease_release,
869-
)
870-
else:
871-
await self._report_status(ExporterStatus.LEASE_READY, "Ready for commands")
872-
lease_scope.before_lease_hook.set()
873-
874-
await _standalone_shutdown_waiter()
859+
try:
860+
async with create_task_group() as tg:
861+
self._tg = tg
862+
tg.start_soon(self._handle_end_session, lease_scope)
863+
864+
if self.hook_executor:
865+
await self.hook_executor.run_before_lease_hook(
866+
lease_scope,
867+
self._report_status,
868+
self.stop,
869+
self._request_lease_release,
870+
)
871+
else:
872+
await self._report_status(ExporterStatus.LEASE_READY, "Ready for commands")
873+
lease_scope.before_lease_hook.set()
874+
875+
await _standalone_shutdown_waiter()
876+
finally:
877+
await self._cleanup_after_lease(lease_scope)
875878

876879
self._lease_context = None
877880
self._tg = None

0 commit comments

Comments
 (0)