[pbs-devel] [PATCH proxmox-backup v10 45/46] api/ui: add flag to allow overwriting in-use marker for s3 backend
Christian Ebner
c.ebner at proxmox.com
Mon Jul 21 18:45:06 CEST 2025
Datastores backed by an s3 object store mark the corresponding bucket
prefix given by the datastore name as in-use to protect from
accidental reuse of the same datastore from other instances.
If the datastore has to be re-created because the Proxmox Backup
Server instance is no longer available, skipping the check and
overwriting the marker with the current hostname is necessary.
Expose this flag to the datastore create api endpoint and expose
it to the web ui and cli command.
Signed-off-by: Christian Ebner <c.ebner at proxmox.com>
---
changes since version 9:
- adapt to `try_from` for S3ObjectKey generation
src/api2/config/datastore.rs | 51 ++++++++++++++-------
src/api2/node/disks/directory.rs | 2 +-
src/api2/node/disks/zfs.rs | 2 +-
src/bin/proxmox_backup_manager/datastore.rs | 6 +++
www/window/DataStoreEdit.js | 22 +++++++++
5 files changed, 64 insertions(+), 19 deletions(-)
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index 019e29c39..6bccfb268 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -113,6 +113,7 @@ pub(crate) fn do_create_datastore(
mut config: SectionConfigData,
datastore: DataStoreConfig,
reuse_datastore: bool,
+ overwrite_in_use: bool,
) -> Result<(), Error> {
let path: PathBuf = datastore.absolute_path().into();
@@ -165,21 +166,24 @@ pub(crate) fn do_create_datastore(
proxmox_async::runtime::block_on(s3_client.head_bucket())
.context("failed to access bucket")?;
- let object_key = S3ObjectKey::try_from(S3_DATASTORE_IN_USE_MARKER)
- .context("failed to generate s3 object key")?;
- if let Some(response) =
- proxmox_async::runtime::block_on(s3_client.get_object(object_key.clone()))
- .context("failed to get in-use marker from bucket")?
- {
- let content = proxmox_async::runtime::block_on(response.content.collect())
- .unwrap_or_default();
- let content =
- String::from_utf8(content.to_bytes().to_vec()).unwrap_or_default();
- let in_use: InUseContent = serde_json::from_str(&content).unwrap_or_default();
- if let Some(hostname) = in_use.hostname {
- bail!("Bucket already contains datastore in use by host {hostname}");
- } else {
- bail!("Bucket already contains datastore in use");
+ if !overwrite_in_use {
+ let object_key = S3ObjectKey::try_from(S3_DATASTORE_IN_USE_MARKER)
+ .context("failed to generate s3 object key")?;
+ if let Some(response) =
+ proxmox_async::runtime::block_on(s3_client.get_object(object_key.clone()))
+ .context("failed to get in-use marker from bucket")?
+ {
+ let content = proxmox_async::runtime::block_on(response.content.collect())
+ .unwrap_or_default();
+ let content =
+ String::from_utf8(content.to_bytes().to_vec()).unwrap_or_default();
+ let in_use: InUseContent =
+ serde_json::from_str(&content).unwrap_or_default();
+ if let Some(hostname) = in_use.hostname {
+ bail!("Bucket already contains datastore in use by host {hostname}");
+ } else {
+ bail!("Bucket already contains datastore in use");
+ }
}
}
backend_s3_client = Some(Arc::new(s3_client));
@@ -272,7 +276,13 @@ pub(crate) fn do_create_datastore(
optional: true,
default: false,
description: "Re-use existing datastore directory."
- }
+ },
+ "overwrite-in-use": {
+ type: Boolean,
+ optional: true,
+ default: false,
+ description: "Overwrite in use marker (S3 backed datastores only)."
+ },
},
},
access: {
@@ -284,6 +294,7 @@ pub(crate) fn do_create_datastore(
pub fn create_datastore(
config: DataStoreConfig,
reuse_datastore: bool,
+ overwrite_in_use: bool,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let lock = pbs_config::datastore::lock_config()?;
@@ -352,7 +363,13 @@ pub fn create_datastore(
auth_id.to_string(),
to_stdout,
move |_worker| {
- do_create_datastore(lock, section_config, config, reuse_datastore)?;
+ do_create_datastore(
+ lock,
+ section_config,
+ config,
+ reuse_datastore,
+ overwrite_in_use,
+ )?;
if let Some(prune_job_config) = prune_job_config {
do_create_prune_job(prune_job_config)?;
diff --git a/src/api2/node/disks/directory.rs b/src/api2/node/disks/directory.rs
index 62f463437..74819079c 100644
--- a/src/api2/node/disks/directory.rs
+++ b/src/api2/node/disks/directory.rs
@@ -254,7 +254,7 @@ pub fn create_datastore_disk(
}
crate::api2::config::datastore::do_create_datastore(
- lock, config, datastore, false,
+ lock, config, datastore, false, false,
)?;
}
diff --git a/src/api2/node/disks/zfs.rs b/src/api2/node/disks/zfs.rs
index b6cf18265..cdb7cc6a1 100644
--- a/src/api2/node/disks/zfs.rs
+++ b/src/api2/node/disks/zfs.rs
@@ -314,7 +314,7 @@ pub fn create_zpool(
}
crate::api2::config::datastore::do_create_datastore(
- lock, config, datastore, false,
+ lock, config, datastore, false, false,
)?;
}
diff --git a/src/bin/proxmox_backup_manager/datastore.rs b/src/bin/proxmox_backup_manager/datastore.rs
index 703974882..45ad27049 100644
--- a/src/bin/proxmox_backup_manager/datastore.rs
+++ b/src/bin/proxmox_backup_manager/datastore.rs
@@ -113,6 +113,12 @@ fn show_datastore(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value
default: false,
description: "Re-use existing datastore directory."
},
+ "overwrite-in-use": {
+ type: Boolean,
+ optional: true,
+ default: false,
+ description: "Overwrite in use marker (S3 backed datastores only)."
+ },
"output-format": {
schema: OUTPUT_FORMAT,
optional: true,
diff --git a/www/window/DataStoreEdit.js b/www/window/DataStoreEdit.js
index 9826313b2..2e5f07598 100644
--- a/www/window/DataStoreEdit.js
+++ b/www/window/DataStoreEdit.js
@@ -76,6 +76,8 @@ Ext.define('PBS.DataStoreEdit', {
let uuidEditField = inputPanel.down('[name=backing-device]');
let bucketField = inputPanel.down('[name=bucket]');
let s3ClientSelector = inputPanel.down('[name=s3client]');
+ let overwriteInUseField =
+ inputPanel.down('[name=overwrite-in-use]');
uuidEditField.setDisabled(!isRemovable);
uuidEditField.allowBlank = !isRemovable;
@@ -89,6 +91,10 @@ Ext.define('PBS.DataStoreEdit', {
s3ClientSelector.allowBlank = !isS3;
s3ClientSelector.setValue('');
+ overwriteInUseField.setHidden(!isS3);
+ overwriteInUseField.setDisabled(!isS3);
+ overwriteInUseField.setValue(false);
+
if (isRemovable) {
pathField.setFieldLabel(gettext('Path on Device'));
pathField.setEmptyText(gettext('A relative path'));
@@ -176,6 +182,22 @@ Ext.define('PBS.DataStoreEdit', {
xtype: 'checkbox',
name: 'reuse-datastore',
fieldLabel: gettext('Reuse existing datastore'),
+ listeners: {
+ change: function (checkbox, selected) {
+ let inputPanel = checkbox.up('inputpanel');
+ let overwriteInUseField =
+ inputPanel.down('[name=overwrite-in-use]');
+ overwriteInUseField.setDisabled(!selected);
+ overwriteInUseField.setValue(false);
+ },
+ },
+ },
+ ],
+ advancedColumn2: [
+ {
+ xtype: 'checkbox',
+ name: 'overwrite-in-use',
+ fieldLabel: gettext('Overwrite in-use marker'),
},
],
--
2.47.2
More information about the pbs-devel
mailing list