test(workspace-server): exercise remote spawn HTTP boundary
This commit is contained in:
@@ -21998,6 +21998,123 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
const STRICT_REMOTE_RUNTIME_ID: &str = "strict-remote-runtime";
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StrictRemoteSpawnState {
|
||||
workdir_status: worker_runtime::catalog::WorkingDirectoryStatus,
|
||||
create_requests: Arc<Mutex<Vec<Value>>>,
|
||||
}
|
||||
|
||||
fn assert_strict_remote_request_headers(headers: &HeaderMap) {
|
||||
assert_eq!(
|
||||
headers
|
||||
.get(worker_runtime::http_server::RUNTIME_WORKSPACE_SCOPE_HEADER)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some(TEST_WORKSPACE_ID)
|
||||
);
|
||||
assert_eq!(
|
||||
headers
|
||||
.get(axum::http::header::AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("Bearer workspace-server-test-runtime-token")
|
||||
);
|
||||
}
|
||||
|
||||
async fn strict_remote_workdir(
|
||||
State(state): State<StrictRemoteSpawnState>,
|
||||
headers: HeaderMap,
|
||||
AxumPath(working_directory_id): AxumPath<String>,
|
||||
) -> Json<Value> {
|
||||
assert_strict_remote_request_headers(&headers);
|
||||
assert_eq!(
|
||||
state.workdir_status.summary.working_directory_id,
|
||||
working_directory_id
|
||||
);
|
||||
Json(json!({ "working_directory": state.workdir_status }))
|
||||
}
|
||||
|
||||
fn strict_remote_worker_response(
|
||||
state: &StrictRemoteSpawnState,
|
||||
request: &runtime_api::CreateWorkerRequest,
|
||||
) -> Value {
|
||||
let attachment = &request.workdir_attachments[0];
|
||||
let profile_source = match &request.profile_source {
|
||||
runtime_api::ProfileSourceArchiveSource::Embedded { archive } => {
|
||||
archive.reference.clone()
|
||||
}
|
||||
runtime_api::ProfileSourceArchiveSource::WorkspaceConfig { archive } => archive.clone(),
|
||||
};
|
||||
json!({
|
||||
"worker": {
|
||||
"worker_ref": { "worker_id": request.worker_id },
|
||||
"worker_id": request.worker_id,
|
||||
"status": "idle",
|
||||
"execution_metadata_available": true,
|
||||
"workspace_id": TEST_WORKSPACE_ID,
|
||||
"workdir_attachments": [{
|
||||
"alias": attachment.alias,
|
||||
"working_directory": state.workdir_status,
|
||||
}],
|
||||
"profile": request.profile,
|
||||
"display_name": request.display_name,
|
||||
"profile_source": profile_source,
|
||||
"config_bundle": request.config_bundle,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn strict_remote_create_worker(
|
||||
State(state): State<StrictRemoteSpawnState>,
|
||||
headers: HeaderMap,
|
||||
body: axum::body::Bytes,
|
||||
) -> Json<Value> {
|
||||
assert_strict_remote_request_headers(&headers);
|
||||
let raw: Value = serde_json::from_slice(&body).unwrap();
|
||||
assert!(
|
||||
raw.get("working_directory").is_none(),
|
||||
"legacy singular working_directory reached Runtime HTTP: {raw}"
|
||||
);
|
||||
let request: runtime_api::CreateWorkerRequest =
|
||||
serde_json::from_value(raw.clone()).unwrap();
|
||||
assert!(request.workdir_attachment_requests.is_empty());
|
||||
assert_eq!(request.workdir_attachments.len(), 1);
|
||||
let attachment = &request.workdir_attachments[0];
|
||||
assert_eq!(attachment.alias.as_str(), "workdir");
|
||||
assert_eq!(
|
||||
attachment.working_directory_id,
|
||||
state.workdir_status.summary.working_directory_id
|
||||
);
|
||||
assert_eq!(attachment.relative_cwd.as_deref(), Some("crates/yoi"));
|
||||
state
|
||||
.create_requests
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.push(raw);
|
||||
|
||||
Json(strict_remote_worker_response(&state, &request))
|
||||
}
|
||||
|
||||
async fn strict_remote_replace_workspace_api(
|
||||
State(state): State<StrictRemoteSpawnState>,
|
||||
headers: HeaderMap,
|
||||
AxumPath(worker_id): AxumPath<String>,
|
||||
Json(workspace_api): Json<runtime_api::WorkerWorkspaceApiRequest>,
|
||||
) -> Json<Value> {
|
||||
assert_strict_remote_request_headers(&headers);
|
||||
assert_eq!(workspace_api.workspace_api.workspace_id, TEST_WORKSPACE_ID);
|
||||
let raw = state
|
||||
.create_requests
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.last()
|
||||
.cloned()
|
||||
.expect("workspace API replacement follows Runtime create");
|
||||
let request: runtime_api::CreateWorkerRequest = serde_json::from_value(raw).unwrap();
|
||||
assert_eq!(request.worker_id.to_string(), worker_id);
|
||||
Json(strict_remote_worker_response(&state, &request))
|
||||
}
|
||||
|
||||
async fn post_guarded_spawn(fixture: &GuardedSpawnFixture, body: Value) -> Response {
|
||||
build_inner_router(fixture.api.clone())
|
||||
.oneshot(
|
||||
@@ -22128,6 +22245,183 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn guarded_spawn_crosses_strict_remote_runtime_http_boundary() {
|
||||
let workspace = tempfile::tempdir().unwrap();
|
||||
init_clean_git_workspace(workspace.path());
|
||||
let api = test_api(workspace.path()).await;
|
||||
register_test_runtime(&api, STRICT_REMOTE_RUNTIME_ID).await;
|
||||
|
||||
let workdir_id = "guarded-strict-remote-workdir".to_string();
|
||||
let repository_id = test_repository_id(&api);
|
||||
let mut workdir_status = WorkdirlessFixtureRuntime::workdir_status(&workdir_id);
|
||||
workdir_status.summary.repository_id = repository_id.clone();
|
||||
let strict_state = StrictRemoteSpawnState {
|
||||
workdir_status,
|
||||
create_requests: Arc::new(Mutex::new(Vec::new())),
|
||||
};
|
||||
let runtime_listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let runtime_endpoint = format!("http://{}", runtime_listener.local_addr().unwrap());
|
||||
let runtime_app = Router::new()
|
||||
.route(
|
||||
"/v1/working-directories/{working_directory_id}",
|
||||
get(strict_remote_workdir),
|
||||
)
|
||||
.route("/v1/workers", post(strict_remote_create_worker))
|
||||
.route(
|
||||
"/v1/workers/{worker_id}/workspace-api",
|
||||
post(strict_remote_replace_workspace_api),
|
||||
)
|
||||
.with_state(strict_state.clone());
|
||||
let runtime_server =
|
||||
tokio::spawn(async move { axum::serve(runtime_listener, runtime_app).await.unwrap() });
|
||||
api.runtime.register_or_replace(
|
||||
RemoteWorkerRuntime::new(
|
||||
RemoteRuntimeConfig {
|
||||
runtime_id: STRICT_REMOTE_RUNTIME_ID.to_string(),
|
||||
workspace_id: Some(TEST_WORKSPACE_ID.to_string()),
|
||||
display_name: "Strict remote Runtime".to_string(),
|
||||
base_url: runtime_endpoint,
|
||||
bearer_token: Some(TEST_RUNTIME_HTTP_TOKEN.to_string()),
|
||||
workspace_authorization: None,
|
||||
strict_public_egress: false,
|
||||
cached_worker_creation_available: true,
|
||||
cached_os: "linux".to_string(),
|
||||
cached_arch: "x86_64".to_string(),
|
||||
cached_status: "active".to_string(),
|
||||
timeout: std::time::Duration::from_secs(2),
|
||||
},
|
||||
TEST_WORKSPACE_ID.to_string(),
|
||||
"http://127.0.0.1:1".to_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let now = now_registry_timestamp();
|
||||
api.store
|
||||
.upsert_workdir_registry(&WorkdirRegistryRecord {
|
||||
workspace_id: TEST_WORKSPACE_ID.to_string(),
|
||||
workdir_id: workdir_id.clone(),
|
||||
display_name: None,
|
||||
runtime_id: STRICT_REMOTE_RUNTIME_ID.to_string(),
|
||||
repository_id: repository_id.clone(),
|
||||
creation_selector: Some("develop".to_string()),
|
||||
creation_ref: None,
|
||||
creation_tree: None,
|
||||
current_selector: Some("develop".to_string()),
|
||||
current_ref: None,
|
||||
current_tree: None,
|
||||
observed_at_epoch_seconds: None,
|
||||
materialization_status: "present".to_string(),
|
||||
cleanliness: "clean".to_string(),
|
||||
created_at: now.clone(),
|
||||
updated_at: now,
|
||||
})
|
||||
.unwrap();
|
||||
let Json(controller) = create_workspace_worker(
|
||||
State(api.clone()),
|
||||
HeaderMap::new(),
|
||||
Json(CreateWorkspaceWorkerRequest {
|
||||
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
||||
display_name: "Strict remote spawn controller".to_string(),
|
||||
profile: Some("builtin:orchestrator".to_string()),
|
||||
ticket_assignment: None,
|
||||
initial_submit: Vec::new(),
|
||||
workdir_attachments: Vec::new(),
|
||||
control_operation_id: None,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let controller = RuntimeWorkerRef::new(controller.runtime_id, controller.worker_id);
|
||||
let mut ticket = ticket::NewTicket::new("Strict remote guarded attachment spawn");
|
||||
ticket.workflow_state = Some(TicketWorkflowState::Queued);
|
||||
ticket.repository_id = Some(repository_id);
|
||||
ticket.ref_selector = Some("develop".to_string());
|
||||
let ticket_id = browser_ticket_backend(&api)
|
||||
.unwrap()
|
||||
.create(ticket)
|
||||
.unwrap()
|
||||
.id;
|
||||
assign_test_orchestrator(&api, &ticket_id);
|
||||
let payload = json!({
|
||||
"runtime_id": STRICT_REMOTE_RUNTIME_ID,
|
||||
"display_name": "Coder · strict remote attachment",
|
||||
"profile": "builtin:coder",
|
||||
"ticket_assignment": {
|
||||
"ticket_id": ticket_id,
|
||||
"operation_id": "guarded-strict-remote-success",
|
||||
},
|
||||
"initial_submit": [
|
||||
{ "kind": "flow", "selector": "builtin:coder-review" },
|
||||
{ "kind": "text", "content": "Implement strict remote attachment Ticket." },
|
||||
],
|
||||
"workdir_attachments": [{
|
||||
"alias": "workdir",
|
||||
"working_directory_id": workdir_id,
|
||||
"relative_cwd": "crates/yoi",
|
||||
}],
|
||||
"control_operation_id": "guarded-strict-remote-success",
|
||||
});
|
||||
assert!(payload.get("working_directory").is_none());
|
||||
|
||||
let response = build_inner_router(api.clone())
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri(format!("/api/w/{TEST_WORKSPACE_ID}/worker-control/workers"))
|
||||
.header("content-type", "application/json")
|
||||
.header("x-yoi-runtime-id", &controller.runtime_id)
|
||||
.header("x-yoi-worker-id", &controller.worker_id)
|
||||
.body(Body::from(payload.to_string()))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let status = response.status();
|
||||
let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
|
||||
assert_eq!(
|
||||
status,
|
||||
StatusCode::OK,
|
||||
"unexpected guarded remote spawn response: {}",
|
||||
String::from_utf8_lossy(&bytes)
|
||||
);
|
||||
let body: BrowserCreateWorkerResponse = serde_json::from_slice(&bytes).unwrap();
|
||||
assert_eq!(body.worker.runtime_id, STRICT_REMOTE_RUNTIME_ID);
|
||||
assert_eq!(body.worker.workdir_attachments.len(), 1);
|
||||
assert_eq!(body.worker.workdir_attachments[0].alias.as_str(), "workdir");
|
||||
assert_eq!(
|
||||
body.worker.workdir_attachments[0]
|
||||
.working_directory
|
||||
.working_directory_id,
|
||||
workdir_id
|
||||
);
|
||||
let requests = strict_state
|
||||
.create_requests
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
assert_eq!(requests.len(), 1);
|
||||
assert!(requests[0].get("working_directory").is_none());
|
||||
assert_eq!(
|
||||
requests[0]["workdir_attachments"].as_array().unwrap().len(),
|
||||
1
|
||||
);
|
||||
drop(requests);
|
||||
assert!(
|
||||
api.store
|
||||
.get_current_ticket_coder_assignment(TEST_WORKSPACE_ID, &ticket_id)
|
||||
.unwrap()
|
||||
.is_some()
|
||||
);
|
||||
assert_eq!(
|
||||
api.store
|
||||
.list_workdir_worker_links(TEST_WORKSPACE_ID, &workdir_id)
|
||||
.unwrap()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
runtime_server.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn guarded_attachment_spawn_rolls_back_runtime_rejection() {
|
||||
let fixture = guarded_spawn_fixture().await;
|
||||
|
||||
Reference in New Issue
Block a user