LCOV - code coverage report
Current view: top level - pageserver/src/tenant/timeline - offload.rs (source / functions) Coverage Total Hit
Test: 903780b8ddc62f532be8f220102da7b91c63a235.info Lines: 0.0 % 77 0
Test Date: 2024-10-25 10:10:57 Functions: 0.0 % 5 0

            Line data    Source code
       1              : use std::sync::Arc;
       2              : 
       3              : use super::delete::{delete_local_timeline_directory, DeleteTimelineFlow, DeletionGuard};
       4              : use super::Timeline;
       5              : use crate::span::debug_assert_current_span_has_tenant_and_timeline_id;
       6              : use crate::tenant::{remote_timeline_client, OffloadedTimeline, Tenant, TimelineOrOffloaded};
       7              : 
       8            0 : pub(crate) async fn offload_timeline(
       9            0 :     tenant: &Tenant,
      10            0 :     timeline: &Arc<Timeline>,
      11            0 : ) -> anyhow::Result<()> {
      12            0 :     debug_assert_current_span_has_tenant_and_timeline_id();
      13            0 :     tracing::info!("offloading archived timeline");
      14              : 
      15            0 :     let (timeline, guard) = DeleteTimelineFlow::prepare(tenant, timeline.timeline_id)?;
      16              : 
      17            0 :     let TimelineOrOffloaded::Timeline(timeline) = timeline else {
      18            0 :         tracing::error!("timeline already offloaded, but given timeline object");
      19            0 :         return Ok(());
      20              :     };
      21              : 
      22            0 :     let is_archived = timeline.is_archived();
      23            0 :     match is_archived {
      24            0 :         Some(true) => (),
      25              :         Some(false) => {
      26            0 :             tracing::warn!(?is_archived, "tried offloading a non-archived timeline");
      27            0 :             anyhow::bail!("timeline isn't archived");
      28              :         }
      29              :         None => {
      30            0 :             tracing::warn!(
      31              :                 ?is_archived,
      32            0 :                 "tried offloading a timeline where manifest is not yet available"
      33              :             );
      34            0 :             anyhow::bail!("timeline manifest hasn't been loaded yet");
      35              :         }
      36              :     }
      37              : 
      38              :     // Now that the Timeline is in Stopping state, request all the related tasks to shut down.
      39            0 :     timeline.shutdown(super::ShutdownMode::Hard).await;
      40              : 
      41              :     // TODO extend guard mechanism above with method
      42              :     // to make deletions possible while offloading is in progress
      43              : 
      44            0 :     let conf = &tenant.conf;
      45            0 :     delete_local_timeline_directory(conf, tenant.tenant_shard_id, &timeline).await?;
      46              : 
      47            0 :     remove_timeline_from_tenant(tenant, &timeline, &guard).await?;
      48              : 
      49            0 :     {
      50            0 :         let mut offloaded_timelines = tenant.timelines_offloaded.lock().unwrap();
      51            0 :         offloaded_timelines.insert(
      52            0 :             timeline.timeline_id,
      53            0 :             Arc::new(
      54            0 :                 OffloadedTimeline::from_timeline(&timeline)
      55            0 :                     .expect("we checked above that timeline was ready"),
      56            0 :             ),
      57            0 :         );
      58            0 :     }
      59            0 : 
      60            0 :     // Last step: mark timeline as offloaded in S3
      61            0 :     // TODO: maybe move this step above, right above deletion of the local timeline directory,
      62            0 :     // then there is no potential race condition where we partially offload a timeline, and
      63            0 :     // at the next restart attach it again.
      64            0 :     // For that to happen, we'd need to make the manifest reflect our *intended* state,
      65            0 :     // not our actual state of offloaded timelines.
      66            0 :     let manifest = tenant.tenant_manifest();
      67            0 :     // TODO: generation support
      68            0 :     let generation = remote_timeline_client::TENANT_MANIFEST_GENERATION;
      69            0 :     remote_timeline_client::upload_tenant_manifest(
      70            0 :         &tenant.remote_storage,
      71            0 :         &tenant.tenant_shard_id,
      72            0 :         generation,
      73            0 :         &manifest,
      74            0 :         &tenant.cancel,
      75            0 :     )
      76            0 :     .await?;
      77              : 
      78            0 :     Ok(())
      79            0 : }
      80              : 
      81              : /// It is important that this gets called when DeletionGuard is being held.
      82              : /// For more context see comments in [`DeleteTimelineFlow::prepare`]
      83            0 : async fn remove_timeline_from_tenant(
      84            0 :     tenant: &Tenant,
      85            0 :     timeline: &Timeline,
      86            0 :     _: &DeletionGuard, // using it as a witness
      87            0 : ) -> anyhow::Result<()> {
      88            0 :     // Remove the timeline from the map.
      89            0 :     let mut timelines = tenant.timelines.lock().unwrap();
      90            0 :     let children_exist = timelines
      91            0 :         .iter()
      92            0 :         .any(|(_, entry)| entry.get_ancestor_timeline_id() == Some(timeline.timeline_id));
      93            0 :     // XXX this can happen because `branch_timeline` doesn't check `TimelineState::Stopping`.
      94            0 :     // We already deleted the layer files, so it's probably best to panic.
      95            0 :     // (Ideally, above remove_dir_all is atomic so we don't see this timeline after a restart)
      96            0 :     if children_exist {
      97            0 :         panic!("Timeline grew children while we removed layer files");
      98            0 :     }
      99            0 : 
     100            0 :     timelines
     101            0 :         .remove(&timeline.timeline_id)
     102            0 :         .expect("timeline that we were deleting was concurrently removed from 'timelines' map");
     103            0 : 
     104            0 :     drop(timelines);
     105            0 : 
     106            0 :     Ok(())
     107            0 : }
        

Generated by: LCOV version 2.1-beta