diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs
index 95c62655ef..7a8f37f923 100644
--- a/pageserver/src/http/routes.rs
+++ b/pageserver/src/http/routes.rs
@@ -747,46 +747,6 @@ async fn tenant_ignore_handler(
json_response(StatusCode::OK, ())
}
-async fn tenant_duplicate_handler(
- mut request: Request
,
- cancel: CancellationToken,
-) -> Result, ApiError> {
- let src_tenant_id: TenantId = parse_request_param(&request, "tenant_id")?;
-
- let request_data: TenantCreateRequest = json_request(&mut request).await?;
- let new_tenant_id = request_data.new_tenant_id;
- check_permission(&request, None)?;
-
- let _timer = STORAGE_TIME_GLOBAL
- .get_metric_with_label_values(&[StorageTimeOperation::DuplicateTenant.into()])
- .expect("bug")
- .start_timer();
-
- let tenant_conf =
- TenantConfOpt::try_from(&request_data.config).map_err(ApiError::BadRequest)?;
-
- let state = get_state(&request);
-
- let generation = get_request_generation(state, request_data.generation)?;
-
- let ctx = RequestContext::new(TaskKind::MgmtRequest, DownloadBehavior::Warn);
-
- mgr::duplicate_tenant(
- state.conf,
- tenant_conf,
- src_tenant_id,
- new_tenant_id,
- generation,
- state.tenant_resources(),
- &ctx,
- &cancel,
- )
- .instrument(info_span!("tenant_duplicate", %src_tenant_id, tenant_id = %new_tenant_id))
- .await?;
-
- json_response(StatusCode::CREATED, TenantCreateResponse(new_tenant_id))
-}
-
async fn tenant_list_handler(
request: Request,
_cancel: CancellationToken,
@@ -1829,9 +1789,6 @@ pub fn make_router(
.post("/v1/tenant/:tenant_id/ignore", |r| {
api_handler(r, tenant_ignore_handler)
})
- .post("/v1/tenant/:tenant_id/duplicate", |r| {
- api_handler(r, tenant_duplicate_handler)
- })
.get("/v1/tenant/:tenant_id/timeline/:timeline_id", |r| {
api_handler(r, timeline_detail_handler)
})
diff --git a/pageserver/src/tenant/mgr.rs b/pageserver/src/tenant/mgr.rs
index 4abf4340f0..fafdb205c2 100644
--- a/pageserver/src/tenant/mgr.rs
+++ b/pageserver/src/tenant/mgr.rs
@@ -6,11 +6,9 @@ use rand::{distributions::Alphanumeric, Rng};
use std::borrow::Cow;
use std::collections::HashMap;
use std::ops::Deref;
-use std::str::FromStr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::fs;
-use tokio::io::AsyncSeekExt;
use utils::timeout::{timeout_cancellable, TimeoutCancellableError};
use anyhow::Context;
@@ -32,11 +30,7 @@ use crate::metrics::TENANT_MANAGER as METRICS;
use crate::task_mgr::{self, TaskKind};
use crate::tenant::config::{AttachmentMode, LocationConf, LocationMode, TenantConfOpt};
use crate::tenant::delete::DeleteTenantFlow;
-use crate::tenant::span::debug_assert_current_span_has_tenant_id;
-use crate::tenant::storage_layer::{DeltaLayer, ImageLayer, LayerFileName};
-use crate::tenant::{
- create_tenant_files, remote_timeline_client, AttachedTenantConf, IndexPart, Tenant, TenantState,
-};
+use crate::tenant::{create_tenant_files, AttachedTenantConf, Tenant, TenantState};
use crate::{InitializationOrder, IGNORED_TENANT_FILE_NAME, TEMP_FILE_SUFFIX};
use utils::crashsafe::path_with_suffix_extension;
@@ -741,171 +735,6 @@ pub(crate) async fn create_tenant(
Ok(created_tenant)
}
-#[allow(clippy::too_many_arguments)]
-pub(crate) async fn duplicate_tenant(
- conf: &'static PageServerConf,
- tenant_conf: TenantConfOpt,
- src_tenant_id: TenantId,
- new_tenant_id: TenantId,
- generation: Generation,
- resources: TenantSharedResources,
- ctx: &RequestContext,
- cancel: &CancellationToken,
-) -> Result<(), TenantMapInsertError> {
- debug_assert_current_span_has_tenant_id();
-
- // TODO: would be nice to use tenant_map_insert here, but, we're not ready to create a Tenant object yet
- let tempdir = path_with_suffix_extension(
- conf.tenants_path().join(&new_tenant_id.to_string()),
- &format!("duplication.{TEMP_FILE_SUFFIX}"),
- );
- tokio::fs::remove_dir_all(&tempdir)
- .await
- .or_else(|e| match e.kind() {
- std::io::ErrorKind::NotFound => Ok(()),
- _ => Err(e),
- })
- .context("pre-run clean up tempdir")?;
-
- tokio::fs::create_dir(&tempdir)
- .await
- .context("create tempdir")?;
-
- // Copy the tenant's data in S3
- let remote_storage = resources
- .remote_storage
- .as_ref()
- .context("only works with remote storage")?;
-
- let (remote_src_timelines, other_prefixes) = remote_timeline_client::list_remote_timelines(
- remote_storage,
- src_tenant_id,
- cancel.clone(),
- )
- .await
- .context("list src timelines")?;
-
- if !other_prefixes.is_empty() {
- return Err(TenantMapInsertError::Other(anyhow::anyhow!(
- "unimplemented: handling of other prefixes in src tenant: {:?}",
- other_prefixes
- )));
- }
-
- info!(?remote_src_timelines, "got src timelines");
-
- for timeline_id in remote_src_timelines {
- async {
- let tempdir = tempdir.join(&timeline_id.to_string());
-
- tokio::fs::create_dir(&tempdir)
- .await
- .context("create tempdir for timeline")?;
-
- let remote_src_tl =
- remote_timeline_client::remote_timeline_path(&src_tenant_id, &timeline_id);
- let remote_dst_tl =
- remote_timeline_client::remote_timeline_path(&new_tenant_id, &timeline_id);
-
- let object_names = remote_storage
- .list_prefixes(Some(&remote_src_tl))
- .await
- .context("list timeline remote prefix")?;
-
- for name in object_names {
- async {
- let name = name.object_name().context(
- "list_prefixes return values should always have object_name()=Some",
- )?;
- let remote_src_obj = remote_src_tl.join(name);
- let remote_dst_obj = remote_dst_tl.join(name);
-
- let tmp_obj_filepath = tempdir.join(name);
- let mut tmp_obj_file = tokio::fs::OpenOptions::new()
- .read(true)
- .write(true)
- .create_new(true)
- .open(&tmp_obj_filepath)
- .await
- .context("create temp file")?;
- let mut tmp_dl = remote_storage
- .download(&remote_src_obj)
- .await
- .context("start download")?;
- let tmp_obj_size =
- tokio::io::copy(&mut tmp_dl.download_stream, &mut tmp_obj_file)
- .await
- .context("do the download")?;
-
- if name == IndexPart::FILE_NAME {
- // needs no patching
- } else {
- let name = LayerFileName::from_str(name).map_err(|e: String| {
- anyhow::anyhow!("unknown key in timeline s3 prefix: {name:?}: {e}")
- })?;
- match name {
- LayerFileName::Image(_) => {
- ImageLayer::rewrite_tenant_timeline(
- &tmp_obj_filepath,
- new_tenant_id,
- timeline_id, /* leave as is */
- ctx,
- )
- .await
- .context("rewrite tenant timeline")?;
- }
- LayerFileName::Delta(_) => {
- DeltaLayer::rewrite_tenant_timeline(
- &tmp_obj_filepath,
- new_tenant_id,
- timeline_id, /* leave as is */
- ctx,
- )
- .await
- .context("rewrite tenant timeline")?;
- }
- }
- }
-
- info!(?remote_dst_obj, "uploading");
-
- tmp_obj_file
- .seek(std::io::SeekFrom::Start(0))
- .await
- .context("seek tmp file to beginning for upload")?;
- remote_storage
- .upload(
- tmp_obj_file,
- tmp_obj_size as usize,
- &remote_dst_obj,
- tmp_dl.metadata,
- )
- .await
- .context("upload modified")?;
-
- tokio::fs::remove_file(tmp_obj_filepath)
- .await
- .context("remove temp file")?;
-
- anyhow::Ok(())
- }
- .instrument(info_span!("copy object", object_name=?name))
- .await
- .context("copy object")?;
- }
- anyhow::Ok(())
- }
- .instrument(info_span!("copy_timeline", timeline_id=%timeline_id))
- .await?;
- }
-
- tokio::fs::remove_dir_all(&tempdir)
- .await
- .context("post-run clean up tempdir")?;
-
- attach_tenant(conf, new_tenant_id, generation, tenant_conf, resources, ctx).await
-}
-
#[derive(Debug, thiserror::Error)]
pub(crate) enum SetNewTenantConfigError {
#[error(transparent)]