fix(linstor): prevent orphaned DRBD devices during toggle-disk retry (#1823)

## Summary

Fix bug in toggle-disk retry logic that left orphaned DRBD devices in
kernel.

## Problem

When toggle-disk retry was triggered (e.g., user retries after a failed
operation), the code called `removeLayerData()` to clean up and recreate
the layer stack. However, `removeLayerData()` only removes data from the
controller's database — it does NOT call `drbdadm down` on the
satellite.

This caused DRBD devices to remain in the kernel (visible in `drbdsetup`
but not managed by LINSTOR), occupying ports and blocking subsequent
operations.

## Solution

Changed retry logic to simply repeat the operation with existing layer
data intact. The satellite handles this idempotently without creating
orphaned resources.

## Upstream

- https://github.com/LINBIT/linstor-server/pull/475 (updated)

```release-note
[linstor] Fix orphaned DRBD devices during toggle-disk retry
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added cancel and retry capabilities for disk addition operations
  * Added cancel and retry capabilities for disk removal operations
* Improved cleanup handling for diskless resources with orphaned storage
layers

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Andrei Kvapil 2026-01-07 15:00:07 +01:00 committed by GitHub
commit b8b330ec8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,8 +1,8 @@
diff --git a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
index 1a6f7b7f0..bd447e049 100644
index d93a18014..cc8ce4f04 100644
--- a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
+++ b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
@@ -58,7 +58,9 @@ import com.linbit.linstor.stateflags.StateFlags;
@@ -57,7 +57,9 @@ import com.linbit.linstor.stateflags.StateFlags;
import com.linbit.linstor.storage.StorageException;
import com.linbit.linstor.storage.data.adapter.drbd.DrbdRscData;
import com.linbit.linstor.storage.interfaces.categories.resource.AbsRscLayerObject;
@ -12,7 +12,7 @@ index 1a6f7b7f0..bd447e049 100644
import com.linbit.linstor.storage.utils.LayerUtils;
import com.linbit.linstor.tasks.AutoDiskfulTask;
import com.linbit.linstor.utils.layer.LayerRscUtils;
@@ -317,21 +319,90 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
@@ -387,21 +389,84 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
Resource rsc = ctrlApiDataLoader.loadRsc(nodeName, rscName, true);
@ -61,18 +61,12 @@ index 1a6f7b7f0..bd447e049 100644
+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition()));
+ }
+ // If adding disk and DISK_ADD_REQUESTED is already set, treat as retry
+ // First clean up partially created storage by removing and recreating layer data
+ // Simply retry the operation with existing layer data - satellite will handle it idempotently
+ // NOTE: We don't remove/recreate layer data here because removeLayerData() only deletes
+ // from controller DB without calling drbdadm down on satellite, leaving orphaned DRBD devices
+ errorReporter.logInfo(
+ "Toggle Disk retry on %s/%s - DISK_ADD_REQUESTED already set, cleaning up and retrying",
+ "Toggle Disk retry on %s/%s - DISK_ADD_REQUESTED already set, retrying operation",
+ nodeNameStr, rscNameStr);
+
+ // Remove old layer data and recreate to ensure clean state
+ // This forces satellite to delete any partially created storage and start fresh
+ LayerPayload payload = new LayerPayload();
+ copyDrbdNodeIdIfExists(rsc, payload);
+ List<DeviceLayerKind> layerList = removeLayerData(rsc);
+ ctrlLayerStackHelper.ensureStackDataExists(rsc, layerList, payload);
+
+ ctrlTransactionHelper.commit();
+ return Flux
+ .<ApiCallRc>just(new ApiCallRcImpl())
@ -113,7 +107,7 @@ index 1a6f7b7f0..bd447e049 100644
}
if (!removeDisk && !ctrlVlmCrtApiHelper.isDiskless(rsc))
@@ -342,17 +413,43 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
@@ -412,17 +477,43 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
true
));
}
@ -160,7 +154,7 @@ index 1a6f7b7f0..bd447e049 100644
if (removeDisk)
{
// Prevent removal of the last disk
@@ -1324,6 +1421,30 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
@@ -1446,6 +1537,30 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
}
}
@ -191,7 +185,7 @@ index 1a6f7b7f0..bd447e049 100644
private void markDiskAdded(Resource rscData)
{
try
@@ -1389,6 +1510,41 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
@@ -1511,6 +1626,41 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
return layerData;
}