From be97e0a55b9ab76119b00ef944a4810e3aea0e96 Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Thu, 5 May 2022 14:12:36 +0200 Subject: [PATCH] tape: add namespaces mapping type and the relevant parser for it Signed-off-by: Dominik Csapak --- pbs-api-types/src/datastore.rs | 33 +++++++++++++++ src/api2/tape/restore.rs | 74 ++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/pbs-api-types/src/datastore.rs b/pbs-api-types/src/datastore.rs index 95ef7120..88179832 100644 --- a/pbs-api-types/src/datastore.rs +++ b/pbs-api-types/src/datastore.rs @@ -1464,6 +1464,39 @@ pub const ADMIN_DATASTORE_PRUNE_RETURN_TYPE: ReturnType = ReturnType { .schema(), }; +#[api( + properties: { + store: { + schema: DATASTORE_SCHEMA, + }, + "max-depth": { + schema: NS_MAX_DEPTH_SCHEMA, + optional: true, + }, + }, +)] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +/// A namespace mapping +pub struct TapeRestoreNamespace { + /// The source datastore + pub store: String, + /// The source namespace. Root namespace if omitted. + pub source: Option, + /// The target namespace, + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option, + /// The (optional) recursion depth + #[serde(skip_serializing_if = "Option::is_none")] + pub max_depth: Option, +} + +pub const TAPE_RESTORE_NAMESPACE_SCHEMA: Schema = StringSchema::new("A namespace mapping") + .format(&ApiStringFormat::PropertyString( + &TapeRestoreNamespace::API_SCHEMA, + )) + .schema(); + /// Parse snapshots in the form 'ns/foo/ns/bar/ct/100/1970-01-01T00:00:00Z' /// into a [`BackupNamespace`] and [`BackupDir`] pub fn parse_ns_and_snapshot(input: &str) -> Result<(BackupNamespace, BackupDir), Error> { diff --git a/src/api2/tape/restore.rs b/src/api2/tape/restore.rs index bbd3170c..c91a6e92 100644 --- a/src/api2/tape/restore.rs +++ b/src/api2/tape/restore.rs @@ -10,16 +10,17 @@ use serde_json::Value; use proxmox_io::ReadExt; use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType}; -use proxmox_schema::api; +use proxmox_schema::{api, ApiType}; use proxmox_section_config::SectionConfigData; use proxmox_sys::fs::{replace_file, CreateOptions}; use proxmox_sys::{task_log, task_warn, WorkerTaskContext}; use proxmox_uuid::Uuid; use pbs_api_types::{ - Authid, BackupNamespace, CryptMode, Operation, Userid, DATASTORE_MAP_ARRAY_SCHEMA, - DATASTORE_MAP_LIST_SCHEMA, DRIVE_NAME_SCHEMA, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, - PRIV_TAPE_READ, TAPE_RESTORE_SNAPSHOT_SCHEMA, UPID_SCHEMA, + Authid, BackupNamespace, CryptMode, Operation, TapeRestoreNamespace, Userid, + DATASTORE_MAP_ARRAY_SCHEMA, DATASTORE_MAP_LIST_SCHEMA, DRIVE_NAME_SCHEMA, MAX_NAMESPACE_DEPTH, + PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_TAPE_READ, TAPE_RESTORE_SNAPSHOT_SCHEMA, + UPID_SCHEMA, }; use pbs_config::CachedUserInfo; use pbs_datastore::dynamic_index::DynamicIndexReader; @@ -56,6 +57,71 @@ pub struct DataStoreMap { default: Option>, } +struct NamespaceMap { + map: HashMap>, +} + +impl TryFrom> for NamespaceMap { + type Error = Error; + + fn try_from(mappings: Vec) -> Result { + let mut map = HashMap::new(); + + let mappings = mappings.into_iter().map(|s| { + let value = TapeRestoreNamespace::API_SCHEMA.parse_property_string(&s)?; + let value: TapeRestoreNamespace = serde_json::from_value(value)?; + Ok::<_, Error>(value) + }); + + for mapping in mappings { + let mapping = mapping?; + let source = mapping.source.unwrap_or_default(); + let target = mapping.target.unwrap_or_default(); + let max_depth = mapping.max_depth.unwrap_or(MAX_NAMESPACE_DEPTH); + + let ns_map: &mut HashMap = + map.entry(mapping.store).or_insert_with(HashMap::new); + + if ns_map.insert(source, (target, max_depth)).is_some() { + bail!("duplicate mapping found"); + } + } + + Ok(Self { map }) + } +} + +impl NamespaceMap { + fn used_namespaces<'a>(&self, datastore: &str) -> HashSet { + let mut set = HashSet::new(); + if let Some(mapping) = self.map.get(datastore) { + for (ns, _) in mapping.values() { + set.insert(ns.clone()); + } + } + + set + } + + fn get_namespaces(&self, source_ds: &str, source_ns: &BackupNamespace) -> Vec { + if let Some(mapping) = self.map.get(source_ds) { + return mapping + .iter() + .filter_map(|(ns, (target_ns, max_depth))| { + // filter out prefixes which are too long + if ns.depth() > source_ns.depth() || source_ns.depth() - ns.depth() > *max_depth + { + return None; + } + source_ns.map_prefix(ns, target_ns).ok() + }) + .collect(); + } + + vec![] + } +} + impl TryFrom for DataStoreMap { type Error = Error;