[pbs-devel] [PATCH proxmox-backup v9 45/46] api/ui: add flag to allow overwriting in-use marker for s3 backend

Christian Ebner c.ebner at proxmox.com
Sat Jul 19 14:50:34 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 8:
- fix formatting using proxmox-biome

 src/api2/config/datastore.rs                | 49 ++++++++++++++-------
 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, 63 insertions(+), 18 deletions(-)

diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index 9f1cbe237..c91969f47 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,20 +166,23 @@ pub(crate) fn do_create_datastore(
                 proxmox_async::runtime::block_on(s3_client.head_bucket())
                     .context("failed to access bucket")?;
 
-                let object_key = S3ObjectKey::from(S3_DATASTORE_IN_USE_MARKER);
-                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::from(S3_DATASTORE_IN_USE_MARKER);
+                    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));
@@ -270,7 +274,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: {
@@ -282,6 +292,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()?;
@@ -350,7 +361,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