Skip to content

Commit cb3efa5

Browse files
committed
test(sandbox): add sparse-policy revision-2 acknowledgement e2e
Regression for #2159: create a sandbox with the network-only policy-advisor fixture, which the supervisor enriches with baseline filesystem paths during startup (creating revision 2, superseding revision 1). Assert the effective policy reaches revision 2 and no revision remains Pending once the supervisor acknowledges the load. Adds SandboxGuard::create_keep_with_args to create a kept sandbox with an initial --policy. Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
1 parent 7128d88 commit cb3efa5

2 files changed

Lines changed: 94 additions & 5 deletions

File tree

e2e/rust/src/harness/sandbox.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,30 @@ impl SandboxGuard {
126126
pub async fn create_keep(
127127
command: &[&str],
128128
ready_marker: &str,
129+
) -> Result<Self, String> {
130+
Self::create_keep_with_args(&[], command, ready_marker).await
131+
}
132+
133+
/// Like [`SandboxGuard::create_keep`], but forwards extra flags to
134+
/// `sandbox create` (e.g. `--policy <file>`, `--name <n>`) before the
135+
/// `-- <command>` separator.
136+
///
137+
/// # Errors
138+
///
139+
/// Returns an error if the process exits prematurely, the ready marker is
140+
/// not seen within [`SANDBOX_READY_TIMEOUT`], or the sandbox name cannot
141+
/// be parsed.
142+
pub async fn create_keep_with_args(
143+
create_args: &[&str],
144+
command: &[&str],
145+
ready_marker: &str,
129146
) -> Result<Self, String> {
130147
let mut cmd = openshell_cmd();
131-
cmd.arg("sandbox")
132-
.arg("create")
133-
.arg("--keep")
134-
.arg("--")
135-
.args(command);
148+
cmd.arg("sandbox").arg("create").arg("--keep");
149+
for arg in create_args {
150+
cmd.arg(arg);
151+
}
152+
cmd.arg("--").args(command);
136153
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
137154

138155
let mut child = cmd

e2e/rust/tests/live_policy_update.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,75 @@ async fn live_policy_update_from_empty_network_policies() {
421421

422422
guard.cleanup().await;
423423
}
424+
425+
/// Regression for #2159: a sparse initial policy that the supervisor enriches
426+
/// with baseline filesystem paths during startup must have its resulting
427+
/// revision acknowledged as loaded, not left `Pending`.
428+
///
429+
/// Reproduction (matches the maintainer's triage): create a sandbox with the
430+
/// network-only `examples/policy-advisor/sandbox-policy.yaml`. The supervisor
431+
/// adds baseline filesystem paths, syncs the enriched policy back to the
432+
/// gateway (creating revision 2, superseding revision 1), builds the OPA engine
433+
/// and then acknowledges revision 2 as loaded. Before the fix, revision 2
434+
/// stayed `Pending` even though the sandbox was `Ready` and the policy was
435+
/// effective.
436+
///
437+
/// NOTE: This exercises the Docker-backed supervisor built from this branch.
438+
/// The exact `policy list` status wording ("Loaded"/"Superseded") may differ by
439+
/// CLI version; the assertions below key on the effective version reaching 2 and
440+
/// no revision remaining `Pending` once the acknowledgement lands.
441+
#[tokio::test]
442+
async fn initial_sparse_policy_is_acknowledged_as_loaded() {
443+
// Repo-relative path to the sparse network-only policy fixture.
444+
let sparse_policy = concat!(
445+
env!("CARGO_MANIFEST_DIR"),
446+
"/../../examples/policy-advisor/sandbox-policy.yaml"
447+
);
448+
449+
let mut guard = SandboxGuard::create_keep_with_args(
450+
&["--name", "e2e-2159-sparse-enrich", "--policy", sparse_policy, "--no-tty"],
451+
&["sh", "-c", "echo Ready && sleep infinity"],
452+
"Ready",
453+
)
454+
.await
455+
.expect("create keep sandbox with sparse policy");
456+
457+
// The enriched revision (2) is synced during startup; the acknowledgement
458+
// (LOADED) is delivered by the supervisor's poll loop shortly after Ready.
459+
// Poll until the effective policy is version 2 and no revision is Pending.
460+
let mut acknowledged = false;
461+
let mut last_list = String::new();
462+
for _ in 0..30 {
463+
let get = run_cli(&["policy", "get", &guard.name]).await;
464+
let version = get.success.then(|| extract_version(&get.output)).flatten();
465+
466+
let list = run_cli(&["policy", "list", &guard.name]).await;
467+
last_list = list.output.clone();
468+
let pending = list.output.to_lowercase().contains("pending");
469+
470+
if version == Some(2) && list.success && !pending {
471+
acknowledged = true;
472+
break;
473+
}
474+
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
475+
}
476+
477+
assert!(
478+
acknowledged,
479+
"enriched initial policy should reach revision 2 with no Pending revision.\n\
480+
last `policy list` output:\n{last_list}"
481+
);
482+
483+
// Both the superseded original (1) and the loaded enriched revision (2)
484+
// must appear in the revision history.
485+
assert!(
486+
list_output_contains_version(&last_list, 2),
487+
"policy list should contain revision 2:\n{last_list}"
488+
);
489+
assert!(
490+
list_output_contains_version(&last_list, 1),
491+
"policy list should contain revision 1:\n{last_list}"
492+
);
493+
494+
guard.cleanup().await;
495+
}

0 commit comments

Comments
 (0)