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(), |     .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' | /// Parse snapshots in the form 'ns/foo/ns/bar/ct/100/1970-01-01T00:00:00Z' | ||||||
| /// into a [`BackupNamespace`] and [`BackupDir`] | /// into a [`BackupNamespace`] and [`BackupDir`] | ||||||
| pub fn parse_ns_and_snapshot(input: &str) -> Result<(BackupNamespace, BackupDir), Error> { | 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_io::ReadExt; | ||||||
| use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType}; | use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType}; | ||||||
| use proxmox_schema::api; | use proxmox_schema::{api, ApiType}; | ||||||
| use proxmox_section_config::SectionConfigData; | use proxmox_section_config::SectionConfigData; | ||||||
| use proxmox_sys::fs::{replace_file, CreateOptions}; | use proxmox_sys::fs::{replace_file, CreateOptions}; | ||||||
| use proxmox_sys::{task_log, task_warn, WorkerTaskContext}; | use proxmox_sys::{task_log, task_warn, WorkerTaskContext}; | ||||||
| use proxmox_uuid::Uuid; | use proxmox_uuid::Uuid; | ||||||
|  |  | ||||||
| use pbs_api_types::{ | use pbs_api_types::{ | ||||||
|     Authid, BackupNamespace, CryptMode, Operation, Userid, DATASTORE_MAP_ARRAY_SCHEMA, |     Authid, BackupNamespace, CryptMode, Operation, TapeRestoreNamespace, Userid, | ||||||
|     DATASTORE_MAP_LIST_SCHEMA, DRIVE_NAME_SCHEMA, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, |     DATASTORE_MAP_ARRAY_SCHEMA, DATASTORE_MAP_LIST_SCHEMA, DRIVE_NAME_SCHEMA, MAX_NAMESPACE_DEPTH, | ||||||
|     PRIV_TAPE_READ, TAPE_RESTORE_SNAPSHOT_SCHEMA, UPID_SCHEMA, |     PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_TAPE_READ, TAPE_RESTORE_SNAPSHOT_SCHEMA, | ||||||
|  |     UPID_SCHEMA, | ||||||
| }; | }; | ||||||
| use pbs_config::CachedUserInfo; | use pbs_config::CachedUserInfo; | ||||||
| use pbs_datastore::dynamic_index::DynamicIndexReader; | use pbs_datastore::dynamic_index::DynamicIndexReader; | ||||||
| @ -56,6 +57,71 @@ pub struct DataStoreMap { | |||||||
|     default: Option<Arc<DataStore>>, |     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 { | impl TryFrom<String> for DataStoreMap { | ||||||
|     type Error = Error; |     type Error = Error; | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user