Skip to content

Commit 5acaaba

Browse files
authored
test(conformance): verify deletion through sandbox list (#3792)
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent b8ffe52 commit 5acaaba

1 file changed

Lines changed: 90 additions & 20 deletions

File tree

‎crates/openshell-conformance/src/scenarios/sandbox_lifecycle.rs‎

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
//! Portable sandbox lifecycle conformance scenarios.
55
6-
use std::time::Duration;
6+
use std::collections::HashSet;
7+
use std::time::{Duration, Instant};
78

89
use serde::Deserialize;
910

@@ -16,10 +17,17 @@ const TRANSITION_INTERVAL: Duration = Duration::from_secs(2);
1617

1718
#[derive(Debug, Deserialize)]
1819
struct SandboxState {
20+
id: String,
1921
name: String,
2022
phase: String,
2123
}
2224

25+
#[derive(Debug, Deserialize)]
26+
struct SandboxListPage {
27+
sandboxes: Vec<SandboxState>,
28+
next_page_token: String,
29+
}
30+
2331
/// Certify sandbox stop, start, and deletion lifecycle behavior.
2432
pub const SANDBOX_LIFECYCLE_SCENARIO: Scenario = Scenario {
2533
name: "sandbox-lifecycle",
@@ -118,9 +126,10 @@ async fn stopped_can_be_deleted(runner: &mut OpenShellRunner) -> Result<(), Stri
118126
.await?;
119127

120128
run_lifecycle_command(runner, "stop", &sandbox_name, "stopped-delete/stop").await?;
121-
wait_for_phase(runner, &sandbox_name, "Stopped", "stopped-delete/stopped").await?;
129+
let sandbox =
130+
wait_for_phase(runner, &sandbox_name, "Stopped", "stopped-delete/stopped").await?;
122131
run_lifecycle_command(runner, "delete", &sandbox_name, "stopped-delete/delete").await?;
123-
wait_for_absence(runner, &sandbox_name, "stopped-delete/deleted").await?;
132+
wait_for_absence(runner, &sandbox.id, &sandbox_name, "stopped-delete/deleted").await?;
124133
runner.forget_sandbox(&sandbox_name);
125134
Ok(())
126135
}
@@ -151,7 +160,9 @@ async fn create_running_sandbox(
151160
.await
152161
.map_err(|error| error.to_string())?;
153162
create.require_success()?;
154-
wait_for_phase(runner, sandbox_name, "Ready", &format!("{step}/ready")).await
163+
wait_for_phase(runner, sandbox_name, "Ready", &format!("{step}/ready"))
164+
.await
165+
.map(|_| ())
155166
}
156167

157168
async fn run_lifecycle_command(
@@ -199,7 +210,7 @@ async fn wait_for_phase(
199210
sandbox_name: &str,
200211
expected_phase: &str,
201212
step: &str,
202-
) -> Result<(), String> {
213+
) -> Result<SandboxState, String> {
203214
let sandbox_name = sandbox_name.to_string();
204215
let expected_phase = expected_phase.to_string();
205216
let step = step.to_string();
@@ -229,7 +240,7 @@ async fn wait_for_phase(
229240
"sandbox get returned {:?}; expected '{sandbox_name}'",
230241
state.name
231242
)),
232-
Ok(state) if state.phase == expected_phase => Poll::Ready(()),
243+
Ok(state) if state.phase == expected_phase => Poll::Ready(state),
233244
Ok(state) => Poll::Pending(format!(
234245
"sandbox '{sandbox_name}' phase is {:?}; expected {expected_phase:?}",
235246
state.phase
@@ -246,9 +257,11 @@ async fn wait_for_phase(
246257

247258
async fn wait_for_absence(
248259
runner: &mut OpenShellRunner,
260+
sandbox_id: &str,
249261
sandbox_name: &str,
250262
step: &str,
251263
) -> Result<(), String> {
264+
let sandbox_id = sandbox_id.to_string();
252265
let sandbox_name = sandbox_name.to_string();
253266
let step = step.to_string();
254267
let poll_step = step.clone();
@@ -257,22 +270,79 @@ async fn wait_for_absence(
257270
&poll_step,
258271
TRANSITION_TIMEOUT,
259272
TRANSITION_INTERVAL,
260-
async move |runner| {
261-
let result = runner
262-
.step(format!("{step}/get"))
263-
.description(format!("sandbox '{sandbox_name}' is no longer retrievable"))
264-
.with_timeout(COMMAND_TIMEOUT)
265-
.run(&["sandbox", "get", &sandbox_name, "--output", "json"])
266-
.await;
267-
match result {
268-
Ok(result) if !result.success() => Poll::Ready(()),
269-
Ok(_) => {
270-
Poll::Pending(format!("sandbox '{sandbox_name}' is still retrievable"))
271-
}
272-
Err(error) => Poll::Pending(error.to_string()),
273-
}
273+
async move |runner| match sandbox_is_listed(runner, &sandbox_id, &sandbox_name, &step)
274+
.await
275+
{
276+
Ok(false) => Poll::Ready(()),
277+
Ok(true) => Poll::Pending(format!(
278+
"sandbox '{sandbox_name}' with ID '{sandbox_id}' is still listed"
279+
)),
280+
Err(error) => Poll::Pending(error),
274281
},
275282
)
276283
.await
277284
.map_err(|error| error.to_string())
278285
}
286+
287+
async fn sandbox_is_listed(
288+
runner: &OpenShellRunner,
289+
sandbox_id: &str,
290+
sandbox_name: &str,
291+
step: &str,
292+
) -> Result<bool, String> {
293+
let deadline = Instant::now() + COMMAND_TIMEOUT;
294+
let mut seen_page_tokens = HashSet::new();
295+
let mut page_token = String::new();
296+
let mut page = 0u32;
297+
loop {
298+
let remaining = deadline.saturating_duration_since(Instant::now());
299+
if remaining.is_zero() {
300+
return Err(format!(
301+
"sandbox list observation for '{sandbox_name}' exceeded its {COMMAND_TIMEOUT:?} deadline"
302+
));
303+
}
304+
305+
let result = runner
306+
.step(format!("{step}/list/{page}"))
307+
.description(format!(
308+
"sandbox list confirms whether '{sandbox_name}' with ID '{sandbox_id}' still exists"
309+
))
310+
.with_timeout(remaining)
311+
.run(&[
312+
"sandbox",
313+
"list",
314+
"--page-size",
315+
"1000",
316+
"--page-token",
317+
&page_token,
318+
"--output",
319+
"json",
320+
])
321+
.await
322+
.map_err(|error| error.to_string())?;
323+
result.require_success()?;
324+
325+
let response = result
326+
.json::<SandboxListPage>()
327+
.map_err(|error| error.to_string())?;
328+
if response
329+
.sandboxes
330+
.iter()
331+
.any(|sandbox| sandbox.id == sandbox_id)
332+
{
333+
return Ok(true);
334+
}
335+
if response.next_page_token.is_empty() {
336+
return Ok(false);
337+
}
338+
if !seen_page_tokens.insert(response.next_page_token.clone()) {
339+
return Err(format!(
340+
"sandbox list returned a repeated page token while looking for '{sandbox_name}'"
341+
));
342+
}
343+
page_token = response.next_page_token;
344+
page = page
345+
.checked_add(1)
346+
.ok_or_else(|| "sandbox list page counter overflowed".to_string())?;
347+
}
348+
}

0 commit comments

Comments
 (0)