diff --git a/control_plane/src/attachment_service.rs b/control_plane/src/attachment_service.rs index fcefe0e431..a49bef038c 100644 --- a/control_plane/src/attachment_service.rs +++ b/control_plane/src/attachment_service.rs @@ -24,6 +24,18 @@ pub struct AttachHookResponse { pub gen: Option, } +#[serde_as] +#[derive(Serialize, Deserialize)] +pub struct InspectRequest { + #[serde_as(as = "DisplayFromStr")] + pub tenant_id: TenantId, +} + +#[derive(Serialize, Deserialize)] +pub struct InspectResponse { + pub attachment: Option<(u32, NodeId)>, +} + impl AttachmentService { pub fn from_env(env: &LocalEnv) -> Self { let path = env.base_data_dir.join("attachments.json"); @@ -101,4 +113,29 @@ impl AttachmentService { let response = response.json::()?; Ok(response.gen) } + + pub fn inspect(&self, tenant_id: TenantId) -> anyhow::Result> { + use hyper::StatusCode; + + let url = self + .env + .control_plane_api + .clone() + .unwrap() + .join("inspect") + .unwrap(); + let client = reqwest::blocking::ClientBuilder::new() + .build() + .expect("Failed to construct http client"); + + let request = InspectRequest { tenant_id }; + + let response = client.post(url).json(&request).send()?; + if response.status() != StatusCode::OK { + return Err(anyhow!("Unexpected status {}", response.status())); + } + + let response = response.json::()?; + Ok(response.attachment) + } } diff --git a/control_plane/src/bin/attachment_service.rs b/control_plane/src/bin/attachment_service.rs index 3205703c7d..ddd50ff51d 100644 --- a/control_plane/src/bin/attachment_service.rs +++ b/control_plane/src/bin/attachment_service.rs @@ -32,7 +32,9 @@ use pageserver_api::control_api::{ ValidateResponseTenant, }; -use control_plane::attachment_service::{AttachHookRequest, AttachHookResponse}; +use control_plane::attachment_service::{ + AttachHookRequest, AttachHookResponse, InspectRequest, InspectResponse, +}; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -255,12 +257,28 @@ async fn handle_attach_hook(mut req: Request) -> Result, Ap ) } +async fn handle_inspect(mut req: Request) -> Result, ApiError> { + let inspect_req = json_request::(&mut req).await?; + + let state = get_state(&req).inner.clone(); + let locked = state.write().await; + let tenant_state = locked.tenants.get(&inspect_req.tenant_id); + + json_response( + StatusCode::OK, + InspectResponse { + attachment: tenant_state.and_then(|s| s.pageserver.map(|ps| (s.generation, ps))), + }, + ) +} + fn make_router(persistent_state: PersistentState) -> RouterBuilder { endpoint::make_router() .data(Arc::new(State::new(persistent_state))) .post("/re-attach", |r| request_span(r, handle_re_attach)) .post("/validate", |r| request_span(r, handle_validate)) .post("/attach-hook", |r| request_span(r, handle_attach_hook)) + .post("/inspect", |r| request_span(r, handle_inspect)) } #[tokio::main]