Skip to content

Commit be7af99

Browse files
authored
fix(e2e): override provider readiness supervisor image via environment (#4207)
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
1 parent 6ea9cd0 commit be7af99

3 files changed

Lines changed: 44 additions & 98 deletions

File tree

‎e2e/rust/src/harness/gateway.rs‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct ManagedGateway {
2121
args_file: PathBuf,
2222
log: PathBuf,
2323
pid_file: PathBuf,
24+
supervisor_image: Option<String>,
2425
}
2526

2627
impl ManagedGateway {
@@ -36,6 +37,7 @@ impl ManagedGateway {
3637
args_file: args_file.into(),
3738
log: log.into(),
3839
pid_file: pid_file.into(),
40+
supervisor_image: None,
3941
}
4042
}
4143

@@ -53,9 +55,15 @@ impl ManagedGateway {
5355
args_file: env_path("OPENSHELL_E2E_GATEWAY_ARGS_FILE")?,
5456
log: env_path("OPENSHELL_E2E_GATEWAY_LOG")?,
5557
pid_file: env_path("OPENSHELL_E2E_GATEWAY_PID_FILE")?,
58+
supervisor_image: None,
5659
}))
5760
}
5861

62+
/// Override the supervisor image for gateways started by this handle.
63+
pub fn set_supervisor_image(&mut self, image: &str) {
64+
self.supervisor_image = Some(image.to_owned());
65+
}
66+
5967
/// Start the gateway if it is not already running.
6068
pub fn start(&self) -> Result<(), String> {
6169
if let Some(pid) = self.current_pid()? {
@@ -80,10 +88,15 @@ impl ManagedGateway {
8088
.try_clone()
8189
.map_err(|err| format!("clone gateway log handle: {err}"))?;
8290

83-
let child = Command::new(&self.bin)
91+
let mut command = Command::new(&self.bin);
92+
command
8493
.args(args)
8594
.stdout(Stdio::from(log))
86-
.stderr(Stdio::from(stderr))
95+
.stderr(Stdio::from(stderr));
96+
if let Some(image) = &self.supervisor_image {
97+
command.env("OPENSHELL_SUPERVISOR_IMAGE", image);
98+
}
99+
let child = command
87100
.spawn()
88101
.map_err(|err| format!("start openshell-gateway '{}': {err}", self.bin.display()))?;
89102
let pid = child.id();

‎e2e/rust/tests/provider_readiness.rs‎

Lines changed: 28 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -212,16 +212,14 @@ impl FixtureImage {
212212
// This fixture owns the only test in its binary. The wrapper's gateway is
213213
// private to this run, so replacing its supervisor image cannot affect another
214214
// test while the public fixture CA is installed in the supervisor trust store.
215-
struct GatewayTrustConfig {
216-
path: PathBuf,
217-
original: String,
218-
image_range: std::ops::Range<usize>,
215+
struct GatewayTrustFixture {
216+
directory: PathBuf,
219217
supervisor_image: String,
220218
health_port: u16,
221219
restore_required: bool,
222220
}
223221

224-
impl GatewayTrustConfig {
222+
impl GatewayTrustFixture {
225223
fn load() -> Result<Self, String> {
226224
if std::env::var_os("OPENSHELL_GATEWAY_ENDPOINT").is_some()
227225
|| std::env::var_os("OPENSHELL_E2E_GATEWAY_BIN").is_none()
@@ -233,6 +231,10 @@ impl GatewayTrustConfig {
233231
}
234232
let args_file = std::env::var_os("OPENSHELL_E2E_GATEWAY_ARGS_FILE")
235233
.ok_or("managed gateway argument metadata is missing")?;
234+
let directory = Path::new(&args_file)
235+
.parent()
236+
.ok_or("managed gateway arguments have no parent directory")?
237+
.to_path_buf();
236238
let raw =
237239
std::fs::read(args_file).map_err(|_| "could not read managed gateway arguments")?;
238240
let args = raw
@@ -249,112 +251,46 @@ impl GatewayTrustConfig {
249251
}
250252
Ok(value)
251253
};
252-
let path = PathBuf::from(argument("--config")?);
253254
let health_port = argument("--health-port")?
254255
.parse::<u16>()
255256
.map_err(|_| "managed gateway health port is invalid")?;
256-
let original = std::fs::read_to_string(&path)
257-
.map_err(|_| "could not read managed gateway configuration")?;
258-
let (image_range, supervisor_image) = docker_supervisor_image(&original)?;
257+
let supervisor_image = std::env::var("OPENSHELL_SUPERVISOR_IMAGE")
258+
.map_err(|_| "managed supervisor image is missing")?;
259259
Ok(Self {
260-
path,
261-
original,
262-
image_range,
260+
directory,
263261
supervisor_image,
264262
health_port,
265263
restore_required: false,
266264
})
267265
}
268266

269267
async fn apply(&mut self, image: &str) -> Result<(), String> {
270-
let mut updated = self.original.clone();
271-
updated.replace_range(self.image_range.clone(), image);
272-
// Set the guard before the write: a failed write or restart must still
273-
// flow through explicit restoration of the exact original bytes.
274268
self.restore_required = true;
275-
std::fs::write(&self.path, updated)
276-
.map_err(|_| "could not install fixture supervisor configuration")?;
277-
restart_fixture_gateway(self.health_port).await
269+
restart_fixture_gateway(self.health_port, Some(image)).await
278270
}
279271

280272
async fn restore(&mut self) -> Result<(), String> {
281273
if !self.restore_required {
282274
return Ok(());
283275
}
284-
std::fs::write(&self.path, &self.original)
285-
.map_err(|_| "could not restore original gateway configuration")?;
286-
restart_fixture_gateway(self.health_port)
276+
restart_fixture_gateway(self.health_port, None)
287277
.await
288-
.map_err(|_| "original gateway configuration was restored but restart failed")?;
278+
.map_err(|_| "could not restore original gateway runtime")?;
289279
self.restore_required = false;
290280
Ok(())
291281
}
292282
}
293283

294-
impl Drop for GatewayTrustConfig {
295-
fn drop(&mut self) {
296-
if self.restore_required {
297-
// Cancellation/panic fallback restores disk state only. Normal
298-
// Result paths explicitly restart and verify health; Drop never
299-
// launches a subprocess or hides a failed restart as success.
300-
let _ = std::fs::write(&self.path, &self.original);
301-
}
302-
}
303-
}
304-
305-
fn docker_supervisor_image(config: &str) -> Result<(std::ops::Range<usize>, String), String> {
306-
let mut in_docker = false;
307-
let mut offset = 0;
308-
let mut found = None;
309-
for line in config.split_inclusive('\n') {
310-
let trimmed = line.trim();
311-
if trimmed.starts_with('[') {
312-
in_docker = trimmed == "[openshell.drivers.docker]";
313-
} else if in_docker && let Some((key, value)) = trimmed.split_once('=') {
314-
if key.trim() == "socket_path" {
315-
return Err(
316-
"fixture cannot replace an external Docker driver configuration".to_string(),
317-
);
318-
}
319-
if key.trim() == "supervisor_image" {
320-
// Accept only the wrapper's single-line quoted OCI reference.
321-
// Reject escapes/comments instead of treating general TOML as
322-
// text and accidentally changing a different configuration key.
323-
let image = value
324-
.trim()
325-
.strip_prefix('"')
326-
.and_then(|value| value.strip_suffix('"'))
327-
.filter(|image| {
328-
!image.is_empty()
329-
&& image.bytes().all(|byte| {
330-
byte.is_ascii_alphanumeric() || b"/:@._-".contains(&byte)
331-
})
332-
})
333-
.ok_or("managed supervisor image is not a simple quoted OCI reference")?;
334-
let start = offset
335-
+ line
336-
.find('"')
337-
.ok_or("managed supervisor image is not quoted")?
338-
+ 1;
339-
if found
340-
.replace((start..start + image.len(), image.to_string()))
341-
.is_some()
342-
{
343-
return Err("managed Docker supervisor image is duplicated".to_string());
344-
}
345-
}
346-
}
347-
offset += line.len();
348-
}
349-
found.ok_or_else(|| "managed Docker supervisor image is missing".to_string())
350-
}
351-
352-
async fn restart_fixture_gateway(health_port: u16) -> Result<(), String> {
353-
let gateway = ManagedGateway::from_env()
284+
async fn restart_fixture_gateway(
285+
health_port: u16,
286+
supervisor_image: Option<&str>,
287+
) -> Result<(), String> {
288+
let mut gateway = ManagedGateway::from_env()
354289
.map_err(|_| "could not load managed gateway restart metadata")?
355290
.ok_or("managed gateway restart metadata disappeared")?;
356-
// ManagedGateway bounds graceful shutdown before force-kill. Keep it local:
357-
// its Drop can start a stopped gateway, but never owns configuration restore.
291+
if let Some(image) = supervisor_image {
292+
gateway.set_supervisor_image(image);
293+
}
358294
gateway
359295
.stop()
360296
.map_err(|_| "could not stop fixture gateway")?;
@@ -1298,19 +1234,15 @@ fn check(
12981234
#[allow(clippy::too_many_lines)]
12991235
async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retained_references()
13001236
-> Result<(), String> {
1301-
let mut gateway_config = GatewayTrustConfig::load()?;
1237+
let mut gateway_fixture = GatewayTrustFixture::load()?;
13021238
// Sandbox names are limited to 19 characters. Retain all 64 random bits
13031239
// within that limit so concurrent fixtures still own distinct resources.
13041240
let name = format!("e2e{:016x}", rand::random::<u64>());
13051241
let mut backend = BackendPair::new(&name)?;
13061242
// The wrapper's directory is shared with the host Docker daemon in CI;
13071243
// a job-container-local temporary path cannot back the TLS bind mount.
1308-
let fixture_parent = gateway_config
1309-
.path
1310-
.parent()
1311-
.ok_or("managed gateway configuration has no parent directory")?;
1312-
let directory =
1313-
TempDir::new_in(fixture_parent).map_err(|_| "could not allocate fixture directory")?;
1244+
let directory = TempDir::new_in(&gateway_fixture.directory)
1245+
.map_err(|_| "could not allocate fixture directory")?;
13141246
let context = directory.path().join("image");
13151247
std::fs::create_dir(&context).map_err(|_| "could not allocate public image context")?;
13161248
let backend_tls = directory.path().join("backend-tls");
@@ -1382,9 +1314,9 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
13821314
// user to the supervisor's private bootstrap files.
13831315
std::fs::write(&supervisor_dockerfile, format!(
13841316
"FROM {} AS supervisor\nFROM {} AS trust-bundle\nUSER 0\nCOPY --from=supervisor /etc/ssl/certs/ca-certificates.crt /tmp/ca-certificates.crt\nCOPY fixture-ca.crt /tmp/readiness-fixture-ca.crt\nRUN [\"/usr/bin/python3\", \"-c\", \"from pathlib import Path; bundle = Path('/tmp/ca-certificates.crt'); bundle.write_bytes(bundle.read_bytes() + Path('/tmp/readiness-fixture-ca.crt').read_bytes())\"]\nFROM {}\nCOPY --from=trust-bundle /tmp/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt\n",
1385-
gateway_config.supervisor_image,
1317+
gateway_fixture.supervisor_image,
13861318
image.tag(),
1387-
gateway_config.supervisor_image,
1319+
gateway_fixture.supervisor_image,
13881320
)).map_err(|_| "could not write fixture supervisor Dockerfile")?;
13891321
supervisor_image
13901322
.build(
@@ -1393,7 +1325,7 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
13931325
"build supervisor fixture image",
13941326
)
13951327
.await?;
1396-
gateway_config.apply(supervisor_image.tag()).await?;
1328+
gateway_fixture.apply(supervisor_image.tag()).await?;
13971329
let profile = directory.path().join("profile.json");
13981330
let policy = directory.path().join("policy.json");
13991331
write_profile(&profile, &name, &host, port, python)?;
@@ -1639,7 +1571,7 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
16391571
// Restore the original runtime before removing its replacement. Retain the
16401572
// derived supervisor image if restoration fails, and report that failure
16411573
// even when a lifecycle assertion already failed.
1642-
let gateway_restore = gateway_config.restore().await;
1574+
let gateway_restore = gateway_fixture.restore().await;
16431575
let supervisor_cleanup = if gateway_restore.is_ok() {
16441576
supervisor_image.remove().await
16451577
} else {

‎e2e/with-docker-gateway.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then
526526
fi
527527

528528
SUPERVISOR_IMAGE="$(resolve_docker_supervisor_image)"
529+
export OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE}"
529530
build_local_docker_supervisor_image_if_required "${SUPERVISOR_IMAGE}"
530531
ensure_docker_supervisor_image "${SUPERVISOR_IMAGE}"
531532
echo "Using Docker supervisor image: ${SUPERVISOR_IMAGE}"

0 commit comments

Comments
 (0)