diff --git a/safekeeper/src/handler.rs b/safekeeper/src/handler.rs index 60df5dd372..99f0e90711 100644 --- a/safekeeper/src/handler.rs +++ b/safekeeper/src/handler.rs @@ -218,7 +218,7 @@ impl SafekeeperPostgresHandler { /// Handle IDENTIFY_SYSTEM replication command /// fn handle_identify_system(&mut self, pgb: &mut PostgresBackend) -> Result<(), QueryError> { - let tli = GlobalTimelines::get(self.ttid)?; + let tli = GlobalTimelines::get(self.ttid).map_err(|e| QueryError::Other(e.into()))?; let lsn = if self.is_walproposer_recovery() { // walproposer should get all local WAL until flush_lsn diff --git a/safekeeper/src/http/openapi_spec.yaml b/safekeeper/src/http/openapi_spec.yaml index da225f244b..51ce7589a0 100644 --- a/safekeeper/src/http/openapi_spec.yaml +++ b/safekeeper/src/http/openapi_spec.yaml @@ -119,6 +119,12 @@ paths: $ref: "#/components/responses/ForbiddenError" default: $ref: "#/components/responses/GenericError" + "404": + description: Timeline not found + content: + application/json: + schema: + $ref: "#/components/schemas/NotFoundError" delete: tags: diff --git a/safekeeper/src/http/routes.rs b/safekeeper/src/http/routes.rs index ced9599b36..b157fcb076 100644 --- a/safekeeper/src/http/routes.rs +++ b/safekeeper/src/http/routes.rs @@ -1,6 +1,5 @@ use hyper::{Body, Request, Response, StatusCode, Uri}; -use anyhow::Context; use once_cell::sync::Lazy; use postgres_ffi::WAL_SEGMENT_SIZE; use safekeeper_api::models::SkTimelineInfo; @@ -112,12 +111,7 @@ async fn timeline_status_handler(request: Request) -> Result) -> Result Result<(), QueryError> { let _enter = info_span!("WAL sender", ttid = %spg.ttid).entered(); - let tli = GlobalTimelines::get(spg.ttid)?; + let tli = GlobalTimelines::get(spg.ttid).map_err(|e| QueryError::Other(e.into()))?; // spawn the background thread which receives HotStandbyFeedback messages. let bg_timeline = Arc::clone(&tli); diff --git a/safekeeper/src/timeline.rs b/safekeeper/src/timeline.rs index 7479741774..98c565cde4 100644 --- a/safekeeper/src/timeline.rs +++ b/safekeeper/src/timeline.rs @@ -1,7 +1,7 @@ //! This module implements Timeline lifecycle management and has all neccessary code //! to glue together SafeKeeper and all other background services. -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Result}; use parking_lot::{Mutex, MutexGuard}; use postgres_ffi::XLogSegNo; use pq_proto::ReplicationFeedback; @@ -13,6 +13,7 @@ use tokio::{ time::Instant, }; use tracing::*; +use utils::http::error::ApiError; use utils::{ id::{NodeId, TenantTimelineId}, lsn::Lsn, @@ -356,6 +357,18 @@ pub enum TimelineError { UninitialinzedPgVersion(TenantTimelineId), } +// Convert to HTTP API error. +impl From for ApiError { + fn from(te: TimelineError) -> ApiError { + match te { + TimelineError::NotFound(ttid) => { + ApiError::NotFound(anyhow!("timeline {} not found", ttid)) + } + _ => ApiError::InternalServerError(anyhow!("{}", te)), + } + } +} + /// Timeline struct manages lifecycle (creation, deletion, restore) of a safekeeper timeline. /// It also holds SharedState and provides mutually exclusive access to it. pub struct Timeline { diff --git a/safekeeper/src/timelines_global_map.rs b/safekeeper/src/timelines_global_map.rs index baef17ffa8..c99ca0a51a 100644 --- a/safekeeper/src/timelines_global_map.rs +++ b/safekeeper/src/timelines_global_map.rs @@ -5,7 +5,7 @@ use crate::safekeeper::ServerInfo; use crate::timeline::{Timeline, TimelineError}; use crate::SafeKeeperConf; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{bail, Context, Result}; use once_cell::sync::Lazy; use serde::Serialize; use std::collections::HashMap; @@ -50,11 +50,11 @@ impl GlobalTimelinesState { } /// Get timeline from the map. Returns error if timeline doesn't exist. - fn get(&self, ttid: &TenantTimelineId) -> Result> { + fn get(&self, ttid: &TenantTimelineId) -> Result, TimelineError> { self.timelines .get(ttid) .cloned() - .ok_or_else(|| anyhow!(TimelineError::NotFound(*ttid))) + .ok_or(TimelineError::NotFound(*ttid)) } } @@ -240,17 +240,17 @@ impl GlobalTimelines { /// Get a timeline from the global map. If it's not present, it doesn't exist on disk, /// or was corrupted and couldn't be loaded on startup. Returned timeline is always valid, /// i.e. loaded in memory and not cancelled. - pub fn get(ttid: TenantTimelineId) -> Result> { + pub fn get(ttid: TenantTimelineId) -> Result, TimelineError> { let res = TIMELINES_STATE.lock().unwrap().get(&ttid); match res { Ok(tli) => { if tli.is_cancelled() { - anyhow::bail!(TimelineError::Cancelled(ttid)); + return Err(TimelineError::Cancelled(ttid)); } Ok(tli) } - Err(e) => Err(e), + _ => res, } }