Skip to content

Commit a9b19a1

Browse files
committed
fix(config): address durable operation review findings
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
1 parent 262be8a commit a9b19a1

9 files changed

Lines changed: 440 additions & 76 deletions

File tree

‎crates/openshell-cli/src/main.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2844,7 +2844,7 @@ async fn run_async() -> Result<()> {
28442844
.await?;
28452845
} else {
28462846
let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?;
2847-
run::sandbox_policy_set(
2847+
let exit_code = run::sandbox_policy_set(
28482848
&ctx.endpoint,
28492849
&name,
28502850
&policy,
@@ -2854,6 +2854,9 @@ async fn run_async() -> Result<()> {
28542854
&tls,
28552855
)
28562856
.await?;
2857+
if exit_code != 0 {
2858+
std::process::exit(exit_code);
2859+
}
28572860
}
28582861
}
28592862
PolicyCommands::Update {
@@ -2872,7 +2875,7 @@ async fn run_async() -> Result<()> {
28722875
timeout,
28732876
} => {
28742877
let name = resolve_sandbox_name(name, &ctx.name, &cli.workspace)?;
2875-
run::sandbox_policy_update(
2878+
let exit_code = run::sandbox_policy_update(
28762879
&ctx.endpoint,
28772880
&name,
28782881
&add_endpoints,
@@ -2891,6 +2894,9 @@ async fn run_async() -> Result<()> {
28912894
&tls,
28922895
)
28932896
.await?;
2897+
if exit_code != 0 {
2898+
std::process::exit(exit_code);
2899+
}
28942900
}
28952901
PolicyCommands::Get {
28962902
name,

‎crates/openshell-cli/src/run.rs‎

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,30 @@ use std::time::{Duration, Instant};
7373
use tonic::{Code, Status};
7474

7575
const PROVISIONAL_CONTAINER_EXIT_RECONCILIATION_TIMEOUT: Duration = Duration::from_secs(5);
76+
const POLICY_WAIT_TIMEOUT_EXIT_CODE: i32 = 124;
77+
78+
fn report_policy_wait_timeout(status: &Status) -> Option<i32> {
79+
if status.code() != Code::DeadlineExceeded {
80+
return None;
81+
}
82+
let operation_id = status
83+
.metadata()
84+
.get("operation-id")
85+
.and_then(|value| value.to_str().ok());
86+
if let Some(operation_id) = operation_id {
87+
eprintln!(
88+
"{} Timeout waiting for policy update operation {}; update remains committed",
89+
"✗".red().bold(),
90+
operation_id
91+
);
92+
} else {
93+
eprintln!(
94+
"{} Timeout waiting for policy update; update remains committed",
95+
"✗".red().bold()
96+
);
97+
}
98+
Some(POLICY_WAIT_TIMEOUT_EXIT_CODE)
99+
}
76100

77101
fn report_config_update_operation(
78102
operation: Option<&ConfigUpdateOperation>,
@@ -4976,7 +5000,7 @@ pub async fn sandbox_policy_set(
49765000
timeout_secs: u64,
49775001
workspace: &str,
49785002
tls: &TlsOptions,
4979-
) -> Result<()> {
5003+
) -> Result<i32> {
49805004
let policy = load_sandbox_policy(Some(policy_path))?
49815005
.ok_or_else(|| miette::miette!("No policy loaded from {policy_path}"))?;
49825006

@@ -4997,7 +5021,7 @@ pub async fn sandbox_policy_set(
49975021
.and_then(|r| r.into_inner().revision)
49985022
.map_or(0, |r| r.version);
49995023

5000-
let response = client
5024+
let response = match client
50015025
.update_config(UpdateConfigRequest {
50025026
sandbox: name.to_string(),
50035027
workspace_scope: Some(openshell_core::proto::workspace_selector(
@@ -5016,7 +5040,16 @@ pub async fn sandbox_policy_set(
50165040
..Default::default()
50175041
})
50185042
.await
5019-
.into_diagnostic()?;
5043+
{
5044+
Ok(response) => response,
5045+
Err(status) if wait => {
5046+
if let Some(exit_code) = report_policy_wait_timeout(&status) {
5047+
return Ok(exit_code);
5048+
}
5049+
return Err(status).into_diagnostic();
5050+
}
5051+
Err(status) => return Err(status).into_diagnostic(),
5052+
};
50205053

50215054
let resp = response.into_inner();
50225055

@@ -5027,7 +5060,7 @@ pub async fn sandbox_policy_set(
50275060
resp.version,
50285061
&resp.policy_hash[..12]
50295062
);
5030-
return Ok(());
5063+
return Ok(0);
50315064
}
50325065

50335066
eprintln!(
@@ -5038,10 +5071,11 @@ pub async fn sandbox_policy_set(
50385071
);
50395072

50405073
if !wait {
5041-
return Ok(());
5074+
return Ok(0);
50425075
}
50435076

5044-
report_config_update_operation(resp.operation.as_ref(), resp.version)
5077+
report_config_update_operation(resp.operation.as_ref(), resp.version)?;
5078+
Ok(0)
50455079
}
50465080

50475081
/// Preview or atomically submit explicitly scoped incremental policy operations.
@@ -5063,7 +5097,7 @@ pub async fn sandbox_policy_update(
50635097
timeout_secs: u64,
50645098
workspace: &str,
50655099
tls: &TlsOptions,
5066-
) -> Result<()> {
5100+
) -> Result<i32> {
50675101
if dry_run && wait {
50685102
return Err(miette!("--wait cannot be combined with --dry-run"));
50695103
}
@@ -5112,12 +5146,12 @@ pub async fn sandbox_policy_update(
51125146
);
51135147
print_policy_merge_warnings(&merged.warnings);
51145148
print_sandbox_policy(&merged.policy);
5115-
return Ok(());
5149+
return Ok(0);
51165150
}
51175151

51185152
let current_version = current.version;
51195153
let current_hash = current.policy_hash.clone();
5120-
let response = client
5154+
let response = match client
51215155
.update_config(UpdateConfigRequest {
51225156
sandbox: name.to_string(),
51235157
workspace_scope: Some(openshell_core::proto::workspace_selector(
@@ -5136,8 +5170,16 @@ pub async fn sandbox_policy_update(
51365170
..Default::default()
51375171
})
51385172
.await
5139-
.into_diagnostic()?
5140-
.into_inner();
5173+
{
5174+
Ok(response) => response.into_inner(),
5175+
Err(status) if wait => {
5176+
if let Some(exit_code) = report_policy_wait_timeout(&status) {
5177+
return Ok(exit_code);
5178+
}
5179+
return Err(status).into_diagnostic();
5180+
}
5181+
Err(status) => return Err(status).into_diagnostic(),
5182+
};
51415183

51425184
print_policy_merge_warnings(&merged.warnings);
51435185

@@ -5148,7 +5190,7 @@ pub async fn sandbox_policy_update(
51485190
response.version,
51495191
short_hash(&response.policy_hash)
51505192
);
5151-
return Ok(());
5193+
return Ok(0);
51525194
}
51535195

51545196
eprintln!(
@@ -5159,10 +5201,11 @@ pub async fn sandbox_policy_update(
51595201
);
51605202

51615203
if !wait {
5162-
return Ok(());
5204+
return Ok(0);
51635205
}
51645206

5165-
report_config_update_operation(response.operation.as_ref(), response.version)
5207+
report_config_update_operation(response.operation.as_ref(), response.version)?;
5208+
Ok(0)
51665209
}
51675210

51685211
pub async fn sandbox_policy_get(

‎crates/openshell-cli/tests/sandbox_name_fallback_integration.rs‎

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use openshell_core::proto::{
2828
ServiceStatus, SupervisorMessage, UpdateProviderRequest, WatchSandboxRequest,
2929
};
3030
use std::sync::Arc;
31+
use std::sync::atomic::{AtomicBool, Ordering};
3132
use tempfile::TempDir;
3233
use tokio::net::TcpListener;
3334
use tokio::sync::{Mutex, mpsc};
@@ -41,6 +42,7 @@ use tonic::{Response, Status};
4142
#[derive(Clone, Default)]
4243
struct SandboxState {
4344
last_get_name: Arc<Mutex<Option<String>>>,
45+
timeout_config_updates: Arc<AtomicBool>,
4446
}
4547

4648
#[derive(Clone, Default)]
@@ -488,6 +490,13 @@ impl OpenShell for TestOpenShell {
488490
&self,
489491
_request: tonic::Request<openshell_core::proto::UpdateConfigRequest>,
490492
) -> Result<Response<openshell_core::proto::UpdateConfigResponse>, Status> {
493+
if self.state.timeout_config_updates.load(Ordering::Relaxed) {
494+
let mut status = Status::deadline_exceeded("update remains pending");
495+
status
496+
.metadata_mut()
497+
.insert("operation-id", "operation-timeout-123".parse().unwrap());
498+
return Err(status);
499+
}
491500
Err(Status::unimplemented("not implemented in test"))
492501
}
493502

@@ -497,7 +506,7 @@ impl OpenShell for TestOpenShell {
497506
) -> Result<Response<GetSandboxPolicyStatusResponse>, Status> {
498507
let req = request.into_inner();
499508
assert_eq!(req.sandbox, "my-sandbox");
500-
assert_eq!(req.version, 3);
509+
assert!(matches!(req.version, 0 | 3));
501510
assert!(!req.global);
502511

503512
let policy = SandboxPolicy {
@@ -729,7 +738,7 @@ struct TestServer {
729738
endpoint: String,
730739
tls: TlsOptions,
731740
openshell: TestOpenShell,
732-
_dir: TempDir,
741+
dir: TempDir,
733742
}
734743

735744
async fn run_server() -> TestServer {
@@ -776,7 +785,7 @@ async fn run_server() -> TestServer {
776785
endpoint,
777786
tls,
778787
openshell,
779-
_dir: dir,
788+
dir,
780789
}
781790
}
782791

@@ -1016,3 +1025,65 @@ async fn explicit_name_takes_precedence_over_persisted() {
10161025
"explicit name should be used, not the persisted one"
10171026
);
10181027
}
1028+
1029+
#[tokio::test(flavor = "multi_thread")]
1030+
async fn policy_wait_timeouts_exit_124_for_set_and_update() {
1031+
let ts = run_server().await;
1032+
ts.openshell
1033+
.state
1034+
.timeout_config_updates
1035+
.store(true, Ordering::Relaxed);
1036+
1037+
let config_dir = tempfile::tempdir().unwrap();
1038+
let mtls_dir = config_dir
1039+
.path()
1040+
.join("openshell/gateways/timeout-test/mtls");
1041+
std::fs::create_dir_all(&mtls_dir).unwrap();
1042+
for name in ["ca.crt", "tls.crt", "tls.key"] {
1043+
std::fs::copy(ts.dir.path().join(name), mtls_dir.join(name)).unwrap();
1044+
}
1045+
let policy_dir = tempfile::tempdir().unwrap();
1046+
let policy_path = policy_dir.path().join("policy.yaml");
1047+
std::fs::write(&policy_path, "version: 1\n").unwrap();
1048+
1049+
let common = [
1050+
"--gateway",
1051+
"timeout-test",
1052+
"--gateway-endpoint",
1053+
ts.endpoint.as_str(),
1054+
"policy",
1055+
];
1056+
let commands = [
1057+
vec![
1058+
"set",
1059+
"my-sandbox",
1060+
"--policy",
1061+
policy_path.to_str().unwrap(),
1062+
"--wait",
1063+
"--timeout",
1064+
"1",
1065+
],
1066+
vec![
1067+
"update",
1068+
"my-sandbox",
1069+
"--add-endpoint",
1070+
"api.example.com:443",
1071+
"--wait",
1072+
"--timeout",
1073+
"1",
1074+
],
1075+
];
1076+
1077+
for args in commands {
1078+
let output = std::process::Command::new(env!("CARGO_BIN_EXE_openshell"))
1079+
.args(common)
1080+
.args(args)
1081+
.env("XDG_CONFIG_HOME", config_dir.path())
1082+
.output()
1083+
.unwrap();
1084+
assert_eq!(output.status.code(), Some(124), "{output:?}");
1085+
let stderr = String::from_utf8_lossy(&output.stderr);
1086+
assert!(stderr.contains("operation-timeout-123"), "{stderr}");
1087+
assert!(stderr.contains("remains committed"), "{stderr}");
1088+
}
1089+
}

0 commit comments

Comments
 (0)