tape: add namespaces mapping type
and the relevant parser for it Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
		| @ -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<BackupNamespace>, | ||||
|     /// The target namespace, | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub target: Option<BackupNamespace>, | ||||
|     /// The (optional) recursion depth | ||||
|     #[serde(skip_serializing_if = "Option::is_none")] | ||||
|     pub max_depth: Option<usize>, | ||||
| } | ||||
|  | ||||
| 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> { | ||||
|  | ||||
| @ -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<Arc<DataStore>>, | ||||
| } | ||||
|  | ||||
| struct NamespaceMap { | ||||
|     map: HashMap<String, HashMap<BackupNamespace, (BackupNamespace, usize)>>, | ||||
| } | ||||
|  | ||||
| impl TryFrom<Vec<String>> for NamespaceMap { | ||||
|     type Error = Error; | ||||
|  | ||||
|     fn try_from(mappings: Vec<String>) -> Result<Self, Error> { | ||||
|         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<BackupNamespace, (BackupNamespace, usize)> = | ||||
|                 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<BackupNamespace> { | ||||
|         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<BackupNamespace> { | ||||
|         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<String> for DataStoreMap { | ||||
|     type Error = Error; | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user