* [pbs-devel] [PATCH proxmox-backup v2 0/4] improve datastore removal/creation
@ 2021-06-02 11:27 Dominik Csapak
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] proxmox-backup-proxy: fix leftover references on datastore removal Dominik Csapak
` (3 more replies)
0 siblings, 4 replies; 9+ messages in thread
From: Dominik Csapak @ 2021-06-02 11:27 UTC (permalink / raw)
To: pbs-devel
by fixing the leftover datastore references
changing the api call that creates the datastore to use a worker
and shows a removal button in the gui
changes from v1:
* renamed one 'refresh_datstores' to 'notify_datastore_removed'
(and the relevant command in the socket)
* changed the create api call to always use a worker,
this makes it necessary to factor the creation code out, so that
we can reuse it in /api2/node/disks/*.rs
* move the remove button from the datastore list to the
datastore option view
Dominik Csapak (4):
proxmox-backup-proxy: fix leftover references on datastore removal
api2/config/datastore: change create datastore api call to a worker
backup/chunk_store: optionally log progress on creation
ui: DataStoreList: add remove button
src/api2/config/datastore.rs | 56 ++++++++++++++++++++++----------
src/api2/node/disks/directory.rs | 15 +++++++--
src/api2/node/disks/zfs.rs | 14 +++++++-
src/backup/chunk_store.rs | 11 ++++---
src/backup/datastore.rs | 16 +++++++++
src/bin/proxmox-backup-proxy.rs | 11 +++++++
src/server.rs | 8 +++++
www/Utils.js | 1 +
www/datastore/OptionView.js | 30 +++++++++++++++++
www/window/DataStoreEdit.js | 1 +
10 files changed, 139 insertions(+), 24 deletions(-)
--
2.20.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 1/4] proxmox-backup-proxy: fix leftover references on datastore removal
2021-06-02 11:27 [pbs-devel] [PATCH proxmox-backup v2 0/4] improve datastore removal/creation Dominik Csapak
@ 2021-06-02 11:27 ` Dominik Csapak
2021-06-04 6:24 ` Dietmar Maurer
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 2/4] api2/config/datastore: change create datastore api call to a worker Dominik Csapak
` (2 subsequent siblings)
3 siblings, 1 reply; 9+ messages in thread
From: Dominik Csapak @ 2021-06-02 11:27 UTC (permalink / raw)
To: pbs-devel
when we remove a datastore via api/cli, the proxy
has sometimes leftover references to that datastore in its
DATASTORE_MAP which includes an open filehandle on the
'.lock' file
this prevents unmounting/exporting the datastore even after removal,
only a reload/restart of the proxy did help
add a command to our command socket, which removes all non
configured datastores from the map, dropping the open filehandle
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/api2/config/datastore.rs | 4 +++-
src/backup/datastore.rs | 16 ++++++++++++++++
src/bin/proxmox-backup-proxy.rs | 11 +++++++++++
src/server.rs | 8 ++++++++
4 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index 6ca98b53..7299c91d 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -403,7 +403,7 @@ pub fn update_datastore(
},
)]
/// Remove a datastore configuration.
-pub fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
+pub async fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
@@ -425,6 +425,8 @@ pub fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Erro
let _ = jobstate::remove_state_file("prune", &name);
let _ = jobstate::remove_state_file("garbage_collection", &name);
+ crate::server::notify_datastore_removed().await?;
+
Ok(())
}
diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs
index 584b2020..6989313d 100644
--- a/src/backup/datastore.rs
+++ b/src/backup/datastore.rs
@@ -69,6 +69,22 @@ impl DataStore {
Ok(datastore)
}
+ /// removes all datastores that are not configured anymore
+ pub fn refresh_datastores() -> Result<(), Error>{
+ let (config, _digest) = datastore::config()?;
+ let mut stores = HashSet::new();
+ for (store, _) in config.sections {
+ stores.insert(store);
+ }
+
+ let mut map = DATASTORE_MAP.lock().unwrap();
+ // removes all elements that are not in the config
+ map.retain(|key, _| {
+ stores.contains(key)
+ });
+ Ok(())
+ }
+
fn open_with_path(store_name: &str, path: &Path, config: DataStoreConfig) -> Result<Self, Error> {
let chunk_store = ChunkStore::open(store_name, path)?;
diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index a53f554a..75d40e21 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -136,6 +136,17 @@ async fn run() -> Result<(), Error> {
},
)?;
+ // to remove references for not configured datastores
+ commando_sock.register_command(
+ "datastore-removed".to_string(),
+ |_value| {
+ if let Err(err) = proxmox_backup::backup::DataStore::refresh_datastores() {
+ log::error!("could not refresh datastores: {}", err);
+ }
+ Ok(Value::Null)
+ }
+ )?;
+
let server = daemon::create_daemon(
([0,0,0,0,0,0,0,0], 8007).into(),
move |listener, ready| {
diff --git a/src/server.rs b/src/server.rs
index ba25617d..c4a36967 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -100,3 +100,11 @@ pub(crate) async fn reload_proxy_certificate() -> Result<(), Error> {
.await?;
Ok(())
}
+
+pub(crate) async fn notify_datastore_removed() -> Result<(), Error> {
+ let proxy_pid = crate::server::read_pid(buildcfg::PROXMOX_BACKUP_PROXY_PID_FN)?;
+ let sock = crate::server::ctrl_sock_from_pid(proxy_pid);
+ let _: Value = crate::server::send_raw_command(sock, "{\"command\":\"datastore-removed\"}\n")
+ .await?;
+ Ok(())
+}
--
2.20.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 2/4] api2/config/datastore: change create datastore api call to a worker
2021-06-02 11:27 [pbs-devel] [PATCH proxmox-backup v2 0/4] improve datastore removal/creation Dominik Csapak
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] proxmox-backup-proxy: fix leftover references on datastore removal Dominik Csapak
@ 2021-06-02 11:27 ` Dominik Csapak
2021-06-04 7:07 ` [pbs-devel] applied: " Dietmar Maurer
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 3/4] backup/chunk_store: optionally log progress on creation Dominik Csapak
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: DataStoreList: add remove button Dominik Csapak
3 siblings, 1 reply; 9+ messages in thread
From: Dominik Csapak @ 2021-06-02 11:27 UTC (permalink / raw)
To: pbs-devel
so that longer running creates (e.g. a slow storage), does not
run in a timeout and we can follow its creation
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/api2/config/datastore.rs | 51 ++++++++++++++++++++++----------
src/api2/node/disks/directory.rs | 15 ++++++++--
src/api2/node/disks/zfs.rs | 14 ++++++++-
www/window/DataStoreEdit.js | 1 +
4 files changed, 62 insertions(+), 19 deletions(-)
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index 7299c91d..4fa76b40 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -5,6 +5,7 @@ use serde_json::Value;
use ::serde::{Deserialize, Serialize};
use proxmox::api::{api, Router, RpcEnvironment, Permission};
+use proxmox::api::section_config::SectionConfigData;
use proxmox::api::schema::parse_property_string;
use proxmox::tools::fs::open_file_locked;
@@ -13,7 +14,7 @@ use crate::backup::*;
use crate::config::cached_user_info::CachedUserInfo;
use crate::config::datastore::{self, DataStoreConfig, DIR_NAME_SCHEMA};
use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY};
-use crate::server::jobstate;
+use crate::server::{jobstate, WorkerTask};
#[api(
input: {
@@ -50,6 +51,25 @@ pub fn list_datastores(
Ok(list.into_iter().filter(filter_by_privs).collect())
}
+pub(crate) fn create_datastore_impl(
+ _lock: std::fs::File,
+ mut config: SectionConfigData,
+ datastore: DataStoreConfig,
+) -> Result<(), Error> {
+ let path: PathBuf = datastore.path.clone().into();
+
+ let backup_user = crate::backup::backup_user()?;
+ let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
+
+ config.set_data(&datastore.name, "datastore", &datastore)?;
+
+ datastore::save_config(&config)?;
+
+ jobstate::create_state_file("prune", &datastore.name)?;
+ jobstate::create_state_file("garbage_collection", &datastore.name)?;
+
+ Ok(())
+}
// fixme: impl. const fn get_object_schema(datastore::DataStoreConfig::API_SCHEMA),
// but this need support for match inside const fn
@@ -116,31 +136,30 @@ pub fn list_datastores(
},
)]
/// Create new datastore config.
-pub fn create_datastore(param: Value) -> Result<(), Error> {
+pub fn create_datastore(
+ param: Value,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<String, Error> {
- let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
+ let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
let datastore: datastore::DataStoreConfig = serde_json::from_value(param)?;
- let (mut config, _digest) = datastore::config()?;
+ let (config, _digest) = datastore::config()?;
if config.sections.get(&datastore.name).is_some() {
bail!("datastore '{}' already exists.", datastore.name);
}
- let path: PathBuf = datastore.path.clone().into();
-
- let backup_user = crate::backup::backup_user()?;
- let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
-
- config.set_data(&datastore.name, "datastore", &datastore)?;
-
- datastore::save_config(&config)?;
-
- jobstate::create_state_file("prune", &datastore.name)?;
- jobstate::create_state_file("garbage_collection", &datastore.name)?;
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
- Ok(())
+ WorkerTask::new_thread(
+ "create-datastore",
+ Some(datastore.name.to_string()),
+ auth_id,
+ false,
+ move |_worker| create_datastore_impl(lock, config, datastore),
+ )
}
#[api(
diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
index c968b333..006e9522 100644
--- a/src/api2/node/disks/directory.rs
+++ b/src/api2/node/disks/directory.rs
@@ -5,6 +5,7 @@ use ::serde::{Deserialize, Serialize};
use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType};
use proxmox::api::section_config::SectionConfigData;
use proxmox::api::router::Router;
+use proxmox::tools::fs::open_file_locked;
use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
use crate::tools::disks::{
@@ -16,7 +17,7 @@ use crate::tools::systemd::{self, types::*};
use crate::server::WorkerTask;
use crate::api2::types::*;
-use crate::config::datastore::DataStoreConfig;
+use crate::config::datastore::{self, DataStoreConfig};
#[api(
properties: {
@@ -179,7 +180,17 @@ pub fn create_datastore_disk(
systemd::start_unit(&mount_unit_name)?;
if add_datastore {
- crate::api2::config::datastore::create_datastore(json!({ "name": name, "path": mount_point }))?
+ let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
+ let datastore: DataStoreConfig =
+ serde_json::from_value(json!({ "name": name, "path": mount_point }))?;
+
+ let (config, _digest) = datastore::config()?;
+
+ if config.sections.get(&datastore.name).is_some() {
+ bail!("datastore '{}' already exists.", datastore.name);
+ }
+
+ crate::api2::config::datastore::create_datastore_impl(lock, config, datastore)?;
}
Ok(())
diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
index 0f93f110..ee8037e2 100644
--- a/src/api2/node/disks/zfs.rs
+++ b/src/api2/node/disks/zfs.rs
@@ -14,12 +14,14 @@ use proxmox::api::{
},
};
use proxmox::api::router::Router;
+use proxmox::tools::fs::open_file_locked;
use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
use crate::tools::disks::{
zpool_list, zpool_status, parse_zpool_status_config_tree, vdev_list_to_tree,
DiskUsageType,
};
+use crate::config::datastore::{self, DataStoreConfig};
use crate::server::WorkerTask;
@@ -372,7 +374,17 @@ pub fn create_zpool(
}
if add_datastore {
- crate::api2::config::datastore::create_datastore(json!({ "name": name, "path": mount_point }))?
+ let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
+ let datastore: DataStoreConfig =
+ serde_json::from_value(json!({ "name": name, "path": mount_point }))?;
+
+ let (config, _digest) = datastore::config()?;
+
+ if config.sections.get(&datastore.name).is_some() {
+ bail!("datastore '{}' already exists.", datastore.name);
+ }
+
+ crate::api2::config::datastore::create_datastore_impl(lock, config, datastore)?;
}
Ok(())
diff --git a/www/window/DataStoreEdit.js b/www/window/DataStoreEdit.js
index c2b2809f..2f67db76 100644
--- a/www/window/DataStoreEdit.js
+++ b/www/window/DataStoreEdit.js
@@ -75,6 +75,7 @@ Ext.define('PBS.DataStoreEdit', {
isAdd: true,
bodyPadding: 0,
+ showProgress: true,
cbindData: function(initialConfig) {
var me = this;
--
2.20.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 3/4] backup/chunk_store: optionally log progress on creation
2021-06-02 11:27 [pbs-devel] [PATCH proxmox-backup v2 0/4] improve datastore removal/creation Dominik Csapak
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] proxmox-backup-proxy: fix leftover references on datastore removal Dominik Csapak
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 2/4] api2/config/datastore: change create datastore api call to a worker Dominik Csapak
@ 2021-06-02 11:27 ` Dominik Csapak
2021-06-04 7:40 ` [pbs-devel] applied: " Dietmar Maurer
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: DataStoreList: add remove button Dominik Csapak
3 siblings, 1 reply; 9+ messages in thread
From: Dominik Csapak @ 2021-06-02 11:27 UTC (permalink / raw)
To: pbs-devel
and enable it for the worker variants
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/api2/config/datastore.rs | 5 +++--
src/api2/node/disks/directory.rs | 2 +-
src/api2/node/disks/zfs.rs | 2 +-
src/backup/chunk_store.rs | 11 +++++++----
4 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index 4fa76b40..4e079cd5 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -55,11 +55,12 @@ pub(crate) fn create_datastore_impl(
_lock: std::fs::File,
mut config: SectionConfigData,
datastore: DataStoreConfig,
+ worker: Option<&dyn crate::task::TaskState>,
) -> Result<(), Error> {
let path: PathBuf = datastore.path.clone().into();
let backup_user = crate::backup::backup_user()?;
- let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
+ let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid, worker)?;
config.set_data(&datastore.name, "datastore", &datastore)?;
@@ -158,7 +159,7 @@ pub fn create_datastore(
Some(datastore.name.to_string()),
auth_id,
false,
- move |_worker| create_datastore_impl(lock, config, datastore),
+ move |worker| create_datastore_impl(lock, config, datastore, Some(&worker)),
)
}
diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
index 006e9522..4eadf867 100644
--- a/src/api2/node/disks/directory.rs
+++ b/src/api2/node/disks/directory.rs
@@ -190,7 +190,7 @@ pub fn create_datastore_disk(
bail!("datastore '{}' already exists.", datastore.name);
}
- crate::api2::config::datastore::create_datastore_impl(lock, config, datastore)?;
+ crate::api2::config::datastore::create_datastore_impl(lock, config, datastore, Some(&worker))?;
}
Ok(())
diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
index ee8037e2..ebdc3688 100644
--- a/src/api2/node/disks/zfs.rs
+++ b/src/api2/node/disks/zfs.rs
@@ -384,7 +384,7 @@ pub fn create_zpool(
bail!("datastore '{}' already exists.", datastore.name);
}
- crate::api2::config::datastore::create_datastore_impl(lock, config, datastore)?;
+ crate::api2::config::datastore::create_datastore_impl(lock, config, datastore, Some(&worker))?;
}
Ok(())
diff --git a/src/backup/chunk_store.rs b/src/backup/chunk_store.rs
index 31e8307c..e9cc3897 100644
--- a/src/backup/chunk_store.rs
+++ b/src/backup/chunk_store.rs
@@ -7,6 +7,7 @@ use std::os::unix::io::AsRawFd;
use proxmox::tools::fs::{CreateOptions, create_path, create_dir};
+use crate::task_log;
use crate::tools;
use crate::api2::types::GarbageCollectionStatus;
@@ -61,7 +62,7 @@ impl ChunkStore {
chunk_dir
}
- pub fn create<P>(name: &str, path: P, uid: nix::unistd::Uid, gid: nix::unistd::Gid) -> Result<Self, Error>
+ pub fn create<P>(name: &str, path: P, uid: nix::unistd::Uid, gid: nix::unistd::Gid, worker: Option<&dyn TaskState>) -> Result<Self, Error>
where
P: Into<PathBuf>,
{
@@ -104,7 +105,9 @@ impl ChunkStore {
}
let percentage = (i*100)/(64*1024);
if percentage != last_percentage {
- // eprintln!("ChunkStore::create {}%", percentage);
+ if let Some(worker) = worker {
+ task_log!(worker, "Chunkstore create: {}%", percentage)
+ }
last_percentage = percentage;
}
}
@@ -461,7 +464,7 @@ fn test_chunk_store1() {
assert!(chunk_store.is_err());
let user = nix::unistd::User::from_uid(nix::unistd::Uid::current()).unwrap().unwrap();
- let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid).unwrap();
+ let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid, None).unwrap();
let (chunk, digest) = super::DataChunkBuilder::new(&[0u8, 1u8]).build().unwrap();
@@ -472,7 +475,7 @@ fn test_chunk_store1() {
assert!(exists);
- let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid);
+ let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid, None);
assert!(chunk_store.is_err());
if let Err(_e) = std::fs::remove_dir_all(".testdir") { /* ignore */ }
--
2.20.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: DataStoreList: add remove button
2021-06-02 11:27 [pbs-devel] [PATCH proxmox-backup v2 0/4] improve datastore removal/creation Dominik Csapak
` (2 preceding siblings ...)
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 3/4] backup/chunk_store: optionally log progress on creation Dominik Csapak
@ 2021-06-02 11:27 ` Dominik Csapak
2021-06-04 7:52 ` [pbs-devel] applied: " Dietmar Maurer
3 siblings, 1 reply; 9+ messages in thread
From: Dominik Csapak @ 2021-06-02 11:27 UTC (permalink / raw)
To: pbs-devel
so that a user can remove a datastore from the gui,
though no data is deleted, this has to be done elsewhere (for now)
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
www/Utils.js | 1 +
www/datastore/OptionView.js | 30 ++++++++++++++++++++++++++++++
2 files changed, 31 insertions(+)
diff --git a/www/Utils.js b/www/Utils.js
index f614d77e..6b378355 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -380,6 +380,7 @@ Ext.define('PBS.Utils', {
backup: (type, id) => PBS.Utils.render_datastore_worker_id(id, gettext('Backup')),
'barcode-label-media': [gettext('Drive'), gettext('Barcode-Label Media')],
'catalog-media': [gettext('Drive'), gettext('Catalog Media')],
+ 'delete-datastore': [gettext('Datastore'), gettext('Remove Datastore')],
dircreate: [gettext('Directory Storage'), gettext('Create')],
dirremove: [gettext('Directory'), gettext('Remove')],
'eject-media': [gettext('Drive'), gettext('Eject Media')],
diff --git a/www/datastore/OptionView.js b/www/datastore/OptionView.js
index 723730fd..98152dce 100644
--- a/www/datastore/OptionView.js
+++ b/www/datastore/OptionView.js
@@ -21,6 +21,28 @@ Ext.define('PBS.Datastore.Options', {
edit: function() {
this.getView().run_editor();
},
+
+ removeDatastore: function() {
+ let me = this;
+ let datastore = me.getView().datastore;
+ Ext.create('Proxmox.window.SafeDestroy', {
+ url: `/config/datastore/${datastore}`,
+ item: {
+ id: datastore,
+ },
+ note: gettext('Configuration change only, no data will be deleted.'),
+ autoShow: true,
+ taskName: 'delete-datastore',
+ listeners: {
+ destroy: () => {
+ let navtree = Ext.ComponentQuery.query('navigationtree')[0];
+ navtree.rstore.load();
+ let mainview = me.getView().up('mainview');
+ mainview.getController().redirectTo('pbsDataStores');
+ },
+ },
+ });
+ },
},
tbar: [
@@ -30,6 +52,14 @@ Ext.define('PBS.Datastore.Options', {
disabled: true,
handler: 'edit',
},
+ '->',
+ {
+ xtype: 'proxmoxButton',
+ selModel: null,
+ iconCls: 'fa fa-trash-o',
+ text: gettext('Remove Datastore'),
+ handler: 'removeDatastore',
+ },
],
listeners: {
--
2.20.1
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v2 1/4] proxmox-backup-proxy: fix leftover references on datastore removal
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] proxmox-backup-proxy: fix leftover references on datastore removal Dominik Csapak
@ 2021-06-04 6:24 ` Dietmar Maurer
0 siblings, 0 replies; 9+ messages in thread
From: Dietmar Maurer @ 2021-06-04 6:24 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Dominik Csapak
applied with small modifications
- simplify refresh_datastores (as suggested by Thomas)
- rename refresh_datastores to remove_unused_datastores
On 6/2/21 1:27 PM, Dominik Csapak wrote:
> when we remove a datastore via api/cli, the proxy
> has sometimes leftover references to that datastore in its
> DATASTORE_MAP which includes an open filehandle on the
> '.lock' file
>
> this prevents unmounting/exporting the datastore even after removal,
> only a reload/restart of the proxy did help
>
> add a command to our command socket, which removes all non
> configured datastores from the map, dropping the open filehandle
>
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
> src/api2/config/datastore.rs | 4 +++-
> src/backup/datastore.rs | 16 ++++++++++++++++
> src/bin/proxmox-backup-proxy.rs | 11 +++++++++++
> src/server.rs | 8 ++++++++
> 4 files changed, 38 insertions(+), 1 deletion(-)
>
> diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
> index 6ca98b53..7299c91d 100644
> --- a/src/api2/config/datastore.rs
> +++ b/src/api2/config/datastore.rs
> @@ -403,7 +403,7 @@ pub fn update_datastore(
> },
> )]
> /// Remove a datastore configuration.
> -pub fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
> +pub async fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
>
> let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
>
> @@ -425,6 +425,8 @@ pub fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Erro
> let _ = jobstate::remove_state_file("prune", &name);
> let _ = jobstate::remove_state_file("garbage_collection", &name);
>
> + crate::server::notify_datastore_removed().await?;
> +
> Ok(())
> }
>
> diff --git a/src/backup/datastore.rs b/src/backup/datastore.rs
> index 584b2020..6989313d 100644
> --- a/src/backup/datastore.rs
> +++ b/src/backup/datastore.rs
> @@ -69,6 +69,22 @@ impl DataStore {
> Ok(datastore)
> }
>
> + /// removes all datastores that are not configured anymore
> + pub fn refresh_datastores() -> Result<(), Error>{
> + let (config, _digest) = datastore::config()?;
> + let mut stores = HashSet::new();
> + for (store, _) in config.sections {
> + stores.insert(store);
> + }
> +
> + let mut map = DATASTORE_MAP.lock().unwrap();
> + // removes all elements that are not in the config
> + map.retain(|key, _| {
> + stores.contains(key)
> + });
> + Ok(())
> + }
> +
> fn open_with_path(store_name: &str, path: &Path, config: DataStoreConfig) -> Result<Self, Error> {
> let chunk_store = ChunkStore::open(store_name, path)?;
>
> diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
> index a53f554a..75d40e21 100644
> --- a/src/bin/proxmox-backup-proxy.rs
> +++ b/src/bin/proxmox-backup-proxy.rs
> @@ -136,6 +136,17 @@ async fn run() -> Result<(), Error> {
> },
> )?;
>
> + // to remove references for not configured datastores
> + commando_sock.register_command(
> + "datastore-removed".to_string(),
> + |_value| {
> + if let Err(err) = proxmox_backup::backup::DataStore::refresh_datastores() {
> + log::error!("could not refresh datastores: {}", err);
> + }
> + Ok(Value::Null)
> + }
> + )?;
> +
> let server = daemon::create_daemon(
> ([0,0,0,0,0,0,0,0], 8007).into(),
> move |listener, ready| {
> diff --git a/src/server.rs b/src/server.rs
> index ba25617d..c4a36967 100644
> --- a/src/server.rs
> +++ b/src/server.rs
> @@ -100,3 +100,11 @@ pub(crate) async fn reload_proxy_certificate() -> Result<(), Error> {
> .await?;
> Ok(())
> }
> +
> +pub(crate) async fn notify_datastore_removed() -> Result<(), Error> {
> + let proxy_pid = crate::server::read_pid(buildcfg::PROXMOX_BACKUP_PROXY_PID_FN)?;
> + let sock = crate::server::ctrl_sock_from_pid(proxy_pid);
> + let _: Value = crate::server::send_raw_command(sock, "{\"command\":\"datastore-removed\"}\n")
> + .await?;
> + Ok(())
> +}
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pbs-devel] applied: [PATCH proxmox-backup v2 2/4] api2/config/datastore: change create datastore api call to a worker
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 2/4] api2/config/datastore: change create datastore api call to a worker Dominik Csapak
@ 2021-06-04 7:07 ` Dietmar Maurer
0 siblings, 0 replies; 9+ messages in thread
From: Dietmar Maurer @ 2021-06-04 7:07 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Dominik Csapak
applied with additional cleanups:
- rename create_datastore_impl to do_create_datastore
- factor out the datastore config lock function
On 6/2/21 1:27 PM, Dominik Csapak wrote:
> so that longer running creates (e.g. a slow storage), does not
> run in a timeout and we can follow its creation
>
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
> src/api2/config/datastore.rs | 51 ++++++++++++++++++++++----------
> src/api2/node/disks/directory.rs | 15 ++++++++--
> src/api2/node/disks/zfs.rs | 14 ++++++++-
> www/window/DataStoreEdit.js | 1 +
> 4 files changed, 62 insertions(+), 19 deletions(-)
>
> diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
> index 7299c91d..4fa76b40 100644
> --- a/src/api2/config/datastore.rs
> +++ b/src/api2/config/datastore.rs
> @@ -5,6 +5,7 @@ use serde_json::Value;
> use ::serde::{Deserialize, Serialize};
>
> use proxmox::api::{api, Router, RpcEnvironment, Permission};
> +use proxmox::api::section_config::SectionConfigData;
> use proxmox::api::schema::parse_property_string;
> use proxmox::tools::fs::open_file_locked;
>
> @@ -13,7 +14,7 @@ use crate::backup::*;
> use crate::config::cached_user_info::CachedUserInfo;
> use crate::config::datastore::{self, DataStoreConfig, DIR_NAME_SCHEMA};
> use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY};
> -use crate::server::jobstate;
> +use crate::server::{jobstate, WorkerTask};
>
> #[api(
> input: {
> @@ -50,6 +51,25 @@ pub fn list_datastores(
> Ok(list.into_iter().filter(filter_by_privs).collect())
> }
>
> +pub(crate) fn create_datastore_impl(
> + _lock: std::fs::File,
> + mut config: SectionConfigData,
> + datastore: DataStoreConfig,
> +) -> Result<(), Error> {
> + let path: PathBuf = datastore.path.clone().into();
> +
> + let backup_user = crate::backup::backup_user()?;
> + let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
> +
> + config.set_data(&datastore.name, "datastore", &datastore)?;
> +
> + datastore::save_config(&config)?;
> +
> + jobstate::create_state_file("prune", &datastore.name)?;
> + jobstate::create_state_file("garbage_collection", &datastore.name)?;
> +
> + Ok(())
> +}
>
> // fixme: impl. const fn get_object_schema(datastore::DataStoreConfig::API_SCHEMA),
> // but this need support for match inside const fn
> @@ -116,31 +136,30 @@ pub fn list_datastores(
> },
> )]
> /// Create new datastore config.
> -pub fn create_datastore(param: Value) -> Result<(), Error> {
> +pub fn create_datastore(
> + param: Value,
> + rpcenv: &mut dyn RpcEnvironment,
> +) -> Result<String, Error> {
>
> - let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
> + let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
>
> let datastore: datastore::DataStoreConfig = serde_json::from_value(param)?;
>
> - let (mut config, _digest) = datastore::config()?;
> + let (config, _digest) = datastore::config()?;
>
> if config.sections.get(&datastore.name).is_some() {
> bail!("datastore '{}' already exists.", datastore.name);
> }
>
> - let path: PathBuf = datastore.path.clone().into();
> -
> - let backup_user = crate::backup::backup_user()?;
> - let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
> -
> - config.set_data(&datastore.name, "datastore", &datastore)?;
> -
> - datastore::save_config(&config)?;
> -
> - jobstate::create_state_file("prune", &datastore.name)?;
> - jobstate::create_state_file("garbage_collection", &datastore.name)?;
> + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
>
> - Ok(())
> + WorkerTask::new_thread(
> + "create-datastore",
> + Some(datastore.name.to_string()),
> + auth_id,
> + false,
> + move |_worker| create_datastore_impl(lock, config, datastore),
> + )
> }
>
> #[api(
> diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
> index c968b333..006e9522 100644
> --- a/src/api2/node/disks/directory.rs
> +++ b/src/api2/node/disks/directory.rs
> @@ -5,6 +5,7 @@ use ::serde::{Deserialize, Serialize};
> use proxmox::api::{api, Permission, RpcEnvironment, RpcEnvironmentType};
> use proxmox::api::section_config::SectionConfigData;
> use proxmox::api::router::Router;
> +use proxmox::tools::fs::open_file_locked;
>
> use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
> use crate::tools::disks::{
> @@ -16,7 +17,7 @@ use crate::tools::systemd::{self, types::*};
> use crate::server::WorkerTask;
>
> use crate::api2::types::*;
> -use crate::config::datastore::DataStoreConfig;
> +use crate::config::datastore::{self, DataStoreConfig};
>
> #[api(
> properties: {
> @@ -179,7 +180,17 @@ pub fn create_datastore_disk(
> systemd::start_unit(&mount_unit_name)?;
>
> if add_datastore {
> - crate::api2::config::datastore::create_datastore(json!({ "name": name, "path": mount_point }))?
> + let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
> + let datastore: DataStoreConfig =
> + serde_json::from_value(json!({ "name": name, "path": mount_point }))?;
> +
> + let (config, _digest) = datastore::config()?;
> +
> + if config.sections.get(&datastore.name).is_some() {
> + bail!("datastore '{}' already exists.", datastore.name);
> + }
> +
> + crate::api2::config::datastore::create_datastore_impl(lock, config, datastore)?;
> }
>
> Ok(())
> diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
> index 0f93f110..ee8037e2 100644
> --- a/src/api2/node/disks/zfs.rs
> +++ b/src/api2/node/disks/zfs.rs
> @@ -14,12 +14,14 @@ use proxmox::api::{
> },
> };
> use proxmox::api::router::Router;
> +use proxmox::tools::fs::open_file_locked;
>
> use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
> use crate::tools::disks::{
> zpool_list, zpool_status, parse_zpool_status_config_tree, vdev_list_to_tree,
> DiskUsageType,
> };
> +use crate::config::datastore::{self, DataStoreConfig};
>
> use crate::server::WorkerTask;
>
> @@ -372,7 +374,17 @@ pub fn create_zpool(
> }
>
> if add_datastore {
> - crate::api2::config::datastore::create_datastore(json!({ "name": name, "path": mount_point }))?
> + let lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
> + let datastore: DataStoreConfig =
> + serde_json::from_value(json!({ "name": name, "path": mount_point }))?;
> +
> + let (config, _digest) = datastore::config()?;
> +
> + if config.sections.get(&datastore.name).is_some() {
> + bail!("datastore '{}' already exists.", datastore.name);
> + }
> +
> + crate::api2::config::datastore::create_datastore_impl(lock, config, datastore)?;
> }
>
> Ok(())
> diff --git a/www/window/DataStoreEdit.js b/www/window/DataStoreEdit.js
> index c2b2809f..2f67db76 100644
> --- a/www/window/DataStoreEdit.js
> +++ b/www/window/DataStoreEdit.js
> @@ -75,6 +75,7 @@ Ext.define('PBS.DataStoreEdit', {
> isAdd: true,
>
> bodyPadding: 0,
> + showProgress: true,
>
> cbindData: function(initialConfig) {
> var me = this;
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pbs-devel] applied: [PATCH proxmox-backup v2 3/4] backup/chunk_store: optionally log progress on creation
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 3/4] backup/chunk_store: optionally log progress on creation Dominik Csapak
@ 2021-06-04 7:40 ` Dietmar Maurer
0 siblings, 0 replies; 9+ messages in thread
From: Dietmar Maurer @ 2021-06-04 7:40 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Dominik Csapak
applied
On 6/2/21 1:27 PM, Dominik Csapak wrote:
> and enable it for the worker variants
>
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
> src/api2/config/datastore.rs | 5 +++--
> src/api2/node/disks/directory.rs | 2 +-
> src/api2/node/disks/zfs.rs | 2 +-
> src/backup/chunk_store.rs | 11 +++++++----
> 4 files changed, 12 insertions(+), 8 deletions(-)
>
> diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
> index 4fa76b40..4e079cd5 100644
> --- a/src/api2/config/datastore.rs
> +++ b/src/api2/config/datastore.rs
> @@ -55,11 +55,12 @@ pub(crate) fn create_datastore_impl(
> _lock: std::fs::File,
> mut config: SectionConfigData,
> datastore: DataStoreConfig,
> + worker: Option<&dyn crate::task::TaskState>,
> ) -> Result<(), Error> {
> let path: PathBuf = datastore.path.clone().into();
>
> let backup_user = crate::backup::backup_user()?;
> - let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
> + let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid, worker)?;
>
> config.set_data(&datastore.name, "datastore", &datastore)?;
>
> @@ -158,7 +159,7 @@ pub fn create_datastore(
> Some(datastore.name.to_string()),
> auth_id,
> false,
> - move |_worker| create_datastore_impl(lock, config, datastore),
> + move |worker| create_datastore_impl(lock, config, datastore, Some(&worker)),
> )
> }
>
> diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
> index 006e9522..4eadf867 100644
> --- a/src/api2/node/disks/directory.rs
> +++ b/src/api2/node/disks/directory.rs
> @@ -190,7 +190,7 @@ pub fn create_datastore_disk(
> bail!("datastore '{}' already exists.", datastore.name);
> }
>
> - crate::api2::config::datastore::create_datastore_impl(lock, config, datastore)?;
> + crate::api2::config::datastore::create_datastore_impl(lock, config, datastore, Some(&worker))?;
> }
>
> Ok(())
> diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
> index ee8037e2..ebdc3688 100644
> --- a/src/api2/node/disks/zfs.rs
> +++ b/src/api2/node/disks/zfs.rs
> @@ -384,7 +384,7 @@ pub fn create_zpool(
> bail!("datastore '{}' already exists.", datastore.name);
> }
>
> - crate::api2::config::datastore::create_datastore_impl(lock, config, datastore)?;
> + crate::api2::config::datastore::create_datastore_impl(lock, config, datastore, Some(&worker))?;
> }
>
> Ok(())
> diff --git a/src/backup/chunk_store.rs b/src/backup/chunk_store.rs
> index 31e8307c..e9cc3897 100644
> --- a/src/backup/chunk_store.rs
> +++ b/src/backup/chunk_store.rs
> @@ -7,6 +7,7 @@ use std::os::unix::io::AsRawFd;
>
> use proxmox::tools::fs::{CreateOptions, create_path, create_dir};
>
> +use crate::task_log;
> use crate::tools;
> use crate::api2::types::GarbageCollectionStatus;
>
> @@ -61,7 +62,7 @@ impl ChunkStore {
> chunk_dir
> }
>
> - pub fn create<P>(name: &str, path: P, uid: nix::unistd::Uid, gid: nix::unistd::Gid) -> Result<Self, Error>
> + pub fn create<P>(name: &str, path: P, uid: nix::unistd::Uid, gid: nix::unistd::Gid, worker: Option<&dyn TaskState>) -> Result<Self, Error>
> where
> P: Into<PathBuf>,
> {
> @@ -104,7 +105,9 @@ impl ChunkStore {
> }
> let percentage = (i*100)/(64*1024);
> if percentage != last_percentage {
> - // eprintln!("ChunkStore::create {}%", percentage);
> + if let Some(worker) = worker {
> + task_log!(worker, "Chunkstore create: {}%", percentage)
> + }
> last_percentage = percentage;
> }
> }
> @@ -461,7 +464,7 @@ fn test_chunk_store1() {
> assert!(chunk_store.is_err());
>
> let user = nix::unistd::User::from_uid(nix::unistd::Uid::current()).unwrap().unwrap();
> - let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid).unwrap();
> + let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid, None).unwrap();
>
> let (chunk, digest) = super::DataChunkBuilder::new(&[0u8, 1u8]).build().unwrap();
>
> @@ -472,7 +475,7 @@ fn test_chunk_store1() {
> assert!(exists);
>
>
> - let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid);
> + let chunk_store = ChunkStore::create("test", &path, user.uid, user.gid, None);
> assert!(chunk_store.is_err());
>
> if let Err(_e) = std::fs::remove_dir_all(".testdir") { /* ignore */ }
^ permalink raw reply [flat|nested] 9+ messages in thread
* [pbs-devel] applied: [PATCH proxmox-backup v2 4/4] ui: DataStoreList: add remove button
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: DataStoreList: add remove button Dominik Csapak
@ 2021-06-04 7:52 ` Dietmar Maurer
0 siblings, 0 replies; 9+ messages in thread
From: Dietmar Maurer @ 2021-06-04 7:52 UTC (permalink / raw)
To: Proxmox Backup Server development discussion, Dominik Csapak
applied
On 6/2/21 1:27 PM, Dominik Csapak wrote:
> so that a user can remove a datastore from the gui,
> though no data is deleted, this has to be done elsewhere (for now)
>
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
> www/Utils.js | 1 +
> www/datastore/OptionView.js | 30 ++++++++++++++++++++++++++++++
> 2 files changed, 31 insertions(+)
>
> diff --git a/www/Utils.js b/www/Utils.js
> index f614d77e..6b378355 100644
> --- a/www/Utils.js
> +++ b/www/Utils.js
> @@ -380,6 +380,7 @@ Ext.define('PBS.Utils', {
> backup: (type, id) => PBS.Utils.render_datastore_worker_id(id, gettext('Backup')),
> 'barcode-label-media': [gettext('Drive'), gettext('Barcode-Label Media')],
> 'catalog-media': [gettext('Drive'), gettext('Catalog Media')],
> + 'delete-datastore': [gettext('Datastore'), gettext('Remove Datastore')],
> dircreate: [gettext('Directory Storage'), gettext('Create')],
> dirremove: [gettext('Directory'), gettext('Remove')],
> 'eject-media': [gettext('Drive'), gettext('Eject Media')],
> diff --git a/www/datastore/OptionView.js b/www/datastore/OptionView.js
> index 723730fd..98152dce 100644
> --- a/www/datastore/OptionView.js
> +++ b/www/datastore/OptionView.js
> @@ -21,6 +21,28 @@ Ext.define('PBS.Datastore.Options', {
> edit: function() {
> this.getView().run_editor();
> },
> +
> + removeDatastore: function() {
> + let me = this;
> + let datastore = me.getView().datastore;
> + Ext.create('Proxmox.window.SafeDestroy', {
> + url: `/config/datastore/${datastore}`,
> + item: {
> + id: datastore,
> + },
> + note: gettext('Configuration change only, no data will be deleted.'),
> + autoShow: true,
> + taskName: 'delete-datastore',
> + listeners: {
> + destroy: () => {
> + let navtree = Ext.ComponentQuery.query('navigationtree')[0];
> + navtree.rstore.load();
> + let mainview = me.getView().up('mainview');
> + mainview.getController().redirectTo('pbsDataStores');
> + },
> + },
> + });
> + },
> },
>
> tbar: [
> @@ -30,6 +52,14 @@ Ext.define('PBS.Datastore.Options', {
> disabled: true,
> handler: 'edit',
> },
> + '->',
> + {
> + xtype: 'proxmoxButton',
> + selModel: null,
> + iconCls: 'fa fa-trash-o',
> + text: gettext('Remove Datastore'),
> + handler: 'removeDatastore',
> + },
> ],
>
> listeners: {
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2021-06-04 7:53 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-02 11:27 [pbs-devel] [PATCH proxmox-backup v2 0/4] improve datastore removal/creation Dominik Csapak
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 1/4] proxmox-backup-proxy: fix leftover references on datastore removal Dominik Csapak
2021-06-04 6:24 ` Dietmar Maurer
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 2/4] api2/config/datastore: change create datastore api call to a worker Dominik Csapak
2021-06-04 7:07 ` [pbs-devel] applied: " Dietmar Maurer
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 3/4] backup/chunk_store: optionally log progress on creation Dominik Csapak
2021-06-04 7:40 ` [pbs-devel] applied: " Dietmar Maurer
2021-06-02 11:27 ` [pbs-devel] [PATCH proxmox-backup v2 4/4] ui: DataStoreList: add remove button Dominik Csapak
2021-06-04 7:52 ` [pbs-devel] applied: " Dietmar Maurer
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal