diff --git a/control_plane/src/compute.rs b/control_plane/src/compute.rs index 3569cc1dbb..3381ca4a04 100644 --- a/control_plane/src/compute.rs +++ b/control_plane/src/compute.rs @@ -10,7 +10,7 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; use zenith_utils::connstring::connection_host_port; use zenith_utils::lsn::Lsn; use zenith_utils::postgres_backend::AuthType; @@ -73,31 +73,6 @@ impl ComputeControlPlane { .unwrap_or(self.base_port) } - // FIXME: see also parse_point_in_time in timelines.rs. - fn parse_point_in_time( - &self, - tenant_id: ZTenantId, - s: &str, - ) -> Result<(ZTimelineId, Option)> { - let _strings = s.split('@'); - // let name = strings.next().unwrap(); - - // let lsn = strings - // .next() - // .map(Lsn::from_str) - // .transpose() - // .context("invalid LSN in point-in-time specification")?; - - // // Resolve the timeline ID, given the human-readable branch name - // let timeline_id = self - // .pageserver - // .branch_get_by_name(&tenant_id, name)? - // .timeline_id; - - // Ok((timeline_id, lsn)) - todo!("TODO kb check more about the '@name' format") - } - pub fn new_node( &mut self, tenantid: ZTenantId, @@ -107,7 +82,7 @@ impl ComputeControlPlane { ) -> Result> { // Resolve the human-readable timeline spec into timeline ID and LSN let (timelineid, lsn) = match timeline_spec { - Some(timeline_spec) => self.parse_point_in_time(tenantid, timeline_spec)?, + Some(timeline_spec) => parse_point_in_time(timeline_spec)?, None => (ZTimelineId::generate(), None), }; @@ -134,6 +109,44 @@ impl ComputeControlPlane { } } +// Parse user-given string that represents a point-in-time. +// +// Variants suported: +// +// Raw timeline id in hex, meaning the end of that timeline: +// bc62e7d612d0e6fe8f99a6dd2f281f9d +// +// A specific LSN on a timeline: +// bc62e7d612d0e6fe8f99a6dd2f281f9d@2/15D3DD8 +// +fn parse_point_in_time(timeline_spec: &str) -> anyhow::Result<(ZTimelineId, Option)> { + let mut strings = timeline_spec.split('@'); + + let name = match strings.next() { + Some(n) => n, + None => bail!("invalid timeline specification: {}", timeline_spec), + }; + let timeline_id = ZTimelineId::from_str(name).with_context(|| { + format!( + "failed to parse the timeline id from specification: {}", + timeline_spec + ) + })?; + + let lsn = strings + .next() + .map(Lsn::from_str) + .transpose() + .with_context(|| { + format!( + "failed to parse the Lsn from timeline specification: {}", + timeline_spec + ) + })?; + + Ok((timeline_id, lsn)) +} + /////////////////////////////////////////////////////////////////////////////// #[derive(Debug)] diff --git a/control_plane/src/storage.rs b/control_plane/src/storage.rs index aed9a757d4..d550bfc064 100644 --- a/control_plane/src/storage.rs +++ b/control_plane/src/storage.rs @@ -16,6 +16,7 @@ use reqwest::blocking::{Client, RequestBuilder, Response}; use reqwest::{IntoUrl, Method}; use thiserror::Error; use zenith_utils::http::error::HttpErrorBody; +use zenith_utils::lsn::Lsn; use zenith_utils::postgres_backend::AuthType; use zenith_utils::zid::{ZTenantId, ZTimelineId}; @@ -348,16 +349,16 @@ impl PageServerNode { pub fn timeline_create( &self, - timeline_id: ZTimelineId, - start_point: String, tenant_id: ZTenantId, + timeline_id: ZTimelineId, + start_lsn: Option, ) -> Result { Ok(self .http_request(Method::POST, format!("{}/timeline", self.http_base_url)) .json(&TimelineCreateRequest { tenant_id, timeline_id, - start_point, + start_lsn, }) .send()? .error_from_body()? diff --git a/pageserver/src/config.rs b/pageserver/src/config.rs index 5a9c7557cc..dc85c83c17 100644 --- a/pageserver/src/config.rs +++ b/pageserver/src/config.rs @@ -392,14 +392,6 @@ impl PageServerConf { self.tenants_path().join(tenantid.to_string()) } - pub fn tags_path(&self, tenantid: &ZTenantId) -> PathBuf { - self.tenant_path(tenantid).join("refs").join("tags") - } - - pub fn tag_path(&self, tag_name: &str, tenantid: &ZTenantId) -> PathBuf { - self.tags_path(tenantid).join(tag_name) - } - pub fn timelines_path(&self, tenantid: &ZTenantId) -> PathBuf { self.tenant_path(tenantid).join(TIMELINES_SEGMENT_NAME) } @@ -408,10 +400,6 @@ impl PageServerConf { self.timelines_path(tenantid).join(timelineid.to_string()) } - pub fn ancestor_path(&self, timelineid: &ZTimelineId, tenantid: &ZTenantId) -> PathBuf { - self.timeline_path(timelineid, tenantid).join("ancestor") - } - // // Postgres distribution paths // diff --git a/pageserver/src/http/models.rs b/pageserver/src/http/models.rs index a6dce33c03..bc0d46a96c 100644 --- a/pageserver/src/http/models.rs +++ b/pageserver/src/http/models.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use zenith_utils::zid::ZTimelineId; +use zenith_utils::{lsn::Lsn, zid::ZTimelineId}; use crate::ZTenantId; use zenith_utils::zid::ZNodeId; @@ -10,7 +10,7 @@ pub struct TimelineCreateRequest { pub tenant_id: ZTenantId, #[serde(with = "hex")] pub timeline_id: ZTimelineId, - pub start_point: String, + pub start_lsn: Option, } #[derive(Serialize, Deserialize)] diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index 5ab1576aa6..34a61cab9c 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -77,12 +77,12 @@ async fn timeline_create_handler(mut request: Request) -> Result, ) -> Result { - let repo = tenant_mgr::get_repository_for_tenant(tenant_id)?; - if conf.timeline_path(&timeline_id, &tenant_id).exists() { bail!("timeline {} already exists", timeline_id); } - let mut startpoint = parse_point_in_time(conf, startpoint_str, &tenant_id)?; + let repo = tenant_mgr::get_repository_for_tenant(tenant_id)?; + + let mut startpoint = PointInTime { + timeline_id, + lsn: start_lsn.unwrap_or(Lsn(0)), + }; + let timeline = repo - .get_timeline(startpoint.timelineid)? + .get_timeline(startpoint.timeline_id)? .local_timeline() .context("Cannot branch off the timeline that's not present locally")?; if startpoint.lsn == Lsn(0) { @@ -297,7 +300,7 @@ pub(crate) fn create_timeline( // Forward entire timeline creation routine to repository // backend, so it can do all needed initialization - repo.branch_timeline(startpoint.timelineid, new_timeline_id, startpoint.lsn)?; + repo.branch_timeline(startpoint.timeline_id, new_timeline_id, startpoint.lsn)?; // Remember the human-readable branch name for the new timeline. // FIXME: there's a race condition, if you create a branch with the same @@ -309,59 +312,9 @@ pub(crate) fn create_timeline( Ok(TimelineInfo { timeline_id: new_timeline_id, latest_valid_lsn: startpoint.lsn, - ancestor_id: Some(startpoint.timelineid.to_string()), + ancestor_id: Some(startpoint.timeline_id.to_string()), ancestor_lsn: Some(startpoint.lsn.to_string()), current_logical_size: 0, current_logical_size_non_incremental: Some(0), }) } - -// -// Parse user-given string that represents a point-in-time. -// -// We support multiple variants: -// -// Raw timeline id in hex, meaning the end of that timeline: -// bc62e7d612d0e6fe8f99a6dd2f281f9d -// -// A specific LSN on a timeline: -// bc62e7d612d0e6fe8f99a6dd2f281f9d@2/15D3DD8 -// -fn parse_point_in_time( - conf: &PageServerConf, - s: &str, - tenantid: &ZTenantId, -) -> Result { - let mut strings = s.split('@'); - let name = strings.next().unwrap(); - - let lsn = strings - .next() - .map(Lsn::from_str) - .transpose() - .context("invalid LSN in point-in-time specification")?; - - // Check if it's a tag - if lsn.is_none() { - let tagpath = conf.tag_path(name, tenantid); - if tagpath.exists() { - let pointstr = fs::read_to_string(tagpath)?; - - return parse_point_in_time(conf, &pointstr, tenantid); - } - } - - // Check if it's a timelineid - // Check if it's timelineid @ LSN - if let Ok(timelineid) = ZTimelineId::from_str(name) { - let tlipath = conf.timeline_path(&timelineid, tenantid); - if tlipath.exists() { - return Ok(PointInTime { - timelineid, - lsn: lsn.unwrap_or(Lsn(0)), - }); - } - } - - bail!("could not parse point-in-time {}", s); -} diff --git a/zenith/src/main.rs b/zenith/src/main.rs index 9f8996a540..7170653754 100644 --- a/zenith/src/main.rs +++ b/zenith/src/main.rs @@ -17,6 +17,7 @@ use walkeeper::defaults::{ DEFAULT_PG_LISTEN_PORT as DEFAULT_SAFEKEEPER_PG_PORT, }; use zenith_utils::auth::{Claims, Scope}; +use zenith_utils::lsn::Lsn; use zenith_utils::postgres_backend::AuthType; use zenith_utils::zid::{ZNodeId, ZTenantId, ZTimelineId}; use zenith_utils::GIT_VERSION; @@ -464,14 +465,15 @@ fn handle_timeline(timeline_match: &ArgMatches, env: &local_env::LocalEnv) -> Re let tenant_id = get_tenantid(timeline_match, env)?; if let Some(timeline_id) = timeline_match.value_of("timeline-id") { - let startpoint_str = timeline_match - .value_of("start-point") - .context("Missing start-point")?; + let start_lsn = timeline_match + .value_of("start-lsn") + .map(|lsn| lsn.parse::()) + .transpose() + .context("Failed to parse start Lsn from the request")?; let timeline_id = timeline_id .parse::() .context("Failed to parse timeline id from the request")?; - let timeline = - pageserver.timeline_create(timeline_id, startpoint_str.to_owned(), tenant_id)?; + let timeline = pageserver.timeline_create(tenant_id, timeline_id, start_lsn)?; println!( "Created timeline '{}' at {:?} for tenant: {}", timeline.timeline_id, timeline.latest_valid_lsn, tenant_id,