[Backport release-1.2] linstor: update piraeus-server to v1.33.2 with selected backports (#2377)

# Description
Backport of #2331 to `release-1.2`.
This commit is contained in:
Andrei Kvapil 2026-04-12 11:22:13 +02:00 committed by GitHub
commit 5674ce73e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1027 additions and 548 deletions

View file

@ -4,7 +4,7 @@ export NAMESPACE=cozy-$(NAME)
include ../../../hack/common-envs.mk
include ../../../hack/package.mk
LINSTOR_VERSION ?= 1.32.3
LINSTOR_VERSION ?= 1.33.2
LINSTOR_CSI_VERSION ?= v1.10.5
image: image-piraeus-server image-linstor-csi

View file

@ -1,14 +1,17 @@
# LINSTOR Server Patches
Custom patches for piraeus-server (linstor-server) v1.32.3.
Custom patches for piraeus-server (linstor-server) v1.33.2.
- **adjust-on-resfile-change.diff** — Use actual device path in res file during toggle-disk; fix LUKS data offset
- Upstream: [#473](https://github.com/LINBIT/linstor-server/pull/473), [#472](https://github.com/LINBIT/linstor-server/pull/472)
- **allow-toggle-disk-retry.diff** — Allow retry and cancellation of failed toggle-disk operations
- Upstream: [#475](https://github.com/LINBIT/linstor-server/pull/475)
- **force-metadata-check-on-disk-add.diff** — Create metadata during toggle-disk from diskless to diskful
- Upstream: [#474](https://github.com/LINBIT/linstor-server/pull/474)
- **fix-duplicate-tcp-ports.diff** — Preserve TCP ports during toggle-disk to prevent port mismatch between controller and satellites
- Upstream: [#476](https://github.com/LINBIT/linstor-server/pull/476) (superseded by this expanded fix)
- **skip-adjust-when-device-inaccessible.diff** — Fix resources stuck in StandAlone after reboot, Unknown state race condition, and encrypted resource deletion
- Upstream: [#477](https://github.com/LINBIT/linstor-server/pull/477)
- **allow-toggle-disk-retry.diff** — Backport maintainer implementation of toggle-disk retry/abort
- Source PR/comment: [#475](https://github.com/LINBIT/linstor-server/pull/475), [maintainer note](https://github.com/LINBIT/linstor-server/pull/475#issuecomment-3949630419)
- Backported from upstream commit: [`3d97f71c9`](https://github.com/LINBIT/linstor-server/commit/3d97f71c95a493588d3d521c63eac4d846935fb3)
- **fix-duplicate-tcp-ports.diff** — Preserve DRBD TCP ports during toggle-disk and avoid redundant `ensureStackDataExists()`
- Source PR/review: [#476](https://github.com/LINBIT/linstor-server/pull/476), [review suggestion](https://github.com/LINBIT/linstor-server/pull/476#discussion_r3007725079)
- Backported from commits: [`79d6375c5`](https://github.com/kvaps/linstor-server/commit/79d6375c55d6181b35a7b7f0fe8dbdfb86e126cd), [`bcc89902f`](https://github.com/kvaps/linstor-server/commit/bcc89902f4f61ac1589dd07ebb7f5aae1935370d)
- **fix-luks-header-size.diff** — Account for LUKS2 `--offset`, metadata/keyslots sizing, and device `optimal_io_size`
- Source PR/comment: [#472](https://github.com/LINBIT/linstor-server/pull/472), [maintainer note](https://github.com/LINBIT/linstor-server/pull/472#issuecomment-3949687603)
- Backported from commits: [`ccc85fbd2`](https://github.com/LINBIT/linstor-server/commit/ccc85fbd2c65f0b97c52403fa80f1efdb886ec4e), [`71b601554`](https://github.com/LINBIT/linstor-server/commit/71b601554f41bcb50cd5bd06989c5b0d3a814acd)
- Note: upstream commit [`3d0402a0c`](https://github.com/LINBIT/linstor-server/commit/3d0402a0c25f0a4b57b380321f10e89982f26e7a) is already included in `v1.33.1`
- **retry-adjust-after-stale-bitmap.diff** — Retry `drbdadm adjust` after detaching a stale local bitmap state
- Source PR: [#491](https://github.com/LINBIT/linstor-server/pull/491)
- Backported from commit: [`51ae50a84`](https://github.com/kvaps/linstor-server/commit/51ae50a84dcb98093f543b819652c750a94d96c9)

View file

@ -1,48 +0,0 @@
diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java
index 36c52ccf8..c0bb7b967 100644
--- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java
+++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/ConfFileBuilder.java
@@ -894,12 +894,16 @@ public class ConfFileBuilder
if (((Volume) vlmData.getVolume()).getFlags().isUnset(localAccCtx, Volume.Flags.DELETE))
{
final String disk;
+ // Check if we're in toggle-disk operation (adding disk to diskless resource)
+ boolean isDiskAdding = vlmData.getVolume().getAbsResource().getStateFlags().isSomeSet(
+ localAccCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING);
if ((!isPeerRsc && vlmData.getDataDevice() == null) ||
(isPeerRsc &&
// FIXME: vlmData.getRscLayerObject().getFlags should be used here
vlmData.getVolume().getAbsResource().disklessForDrbdPeers(accCtx)
) ||
- (!isPeerRsc &&
+ // For toggle-disk: if dataDevice is set and we're adding disk, use the actual device
+ (!isPeerRsc && !isDiskAdding &&
// FIXME: vlmData.getRscLayerObject().getFlags should be used here
vlmData.getVolume().getAbsResource().isDrbdDiskless(accCtx)
)
diff --git a/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java b/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java
index 54dd5c19f..018de58cf 100644
--- a/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java
+++ b/satellite/src/main/java/com/linbit/linstor/layer/luks/CryptSetupCommands.java
@@ -34,6 +34,9 @@ public class CryptSetupCommands implements Luks
private static final Version V2_1_0 = new Version(2, 1, 0);
private static final Version V2_0_0 = new Version(2, 0, 0);
private static final String PBDKF_MAX_MEMORY_KIB = "262144"; // 256 MiB
+ // Fixed LUKS2 data offset in 512-byte sectors (16 MiB = 32768 sectors)
+ // This ensures consistent LUKS header size across all nodes regardless of system defaults
+ private static final String LUKS2_DATA_OFFSET_SECTORS = "32768";
@SuppressWarnings("unused")
private final ErrorReporter errorReporter;
@@ -78,6 +81,11 @@ public class CryptSetupCommands implements Luks
command.add(CRYPTSETUP);
command.add("-q");
command.add("luksFormat");
+ // Always specify explicit offset to ensure consistent LUKS header size across all nodes
+ // Without this, different systems may create LUKS with different header sizes (16MiB vs 32MiB)
+ // which causes "Low.dev. smaller than requested DRBD-dev. size" errors during toggle-disk
+ command.add("--offset");
+ command.add(LUKS2_DATA_OFFSET_SECTORS);
if (version.greaterOrEqual(V2_0_0))
{
command.add("--pbkdf-memory");

View file

@ -1,168 +1,232 @@
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 d93a18014..cc8ce4f04 100644
index d93a18014..a944cb809 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
@@ -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;
+import com.linbit.linstor.storage.interfaces.categories.resource.VlmProviderObject;
import com.linbit.linstor.storage.kinds.DeviceLayerKind;
+import com.linbit.linstor.storage.kinds.DeviceProviderKind;
import com.linbit.linstor.storage.utils.LayerUtils;
import com.linbit.linstor.tasks.AutoDiskfulTask;
import com.linbit.linstor.utils.layer.LayerRscUtils;
@@ -387,21 +389,84 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
@@ -111,6 +111,14 @@ import reactor.util.function.Tuple2;
@Singleton
public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionListener
{
+ private enum ToggleDiskAction
+ {
+ NOOP,
+ NORMAL,
+ ABORT,
+ RETRY
+ }
+
private final AccessContext apiCtx;
private final ScopeRunner scopeRunner;
private final BackgroundRunner backgroundRunner;
@@ -386,69 +394,33 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
"Toggle Disk on %s/%s %s", nodeNameStr, rscNameStr, removeDisk ? "removing disk" : "adding disk");
Resource rsc = ctrlApiDataLoader.loadRsc(nodeName, rscName, true);
+ ResourceDefinition rscDfn = rsc.getResourceDefinition();
+ // Allow retry of the same operation if the previous attempt failed
+ // (the requested flag remains set for retry on reconnection, but we should also allow manual retry)
+ // Also allow cancellation of a failed operation by requesting the opposite operation
if (hasDiskAddRequested(rsc))
{
- if (hasDiskAddRequested(rsc))
- {
- throw new ApiRcException(ApiCallRcImpl.simpleEntry(
- ApiConsts.FAIL_RSC_BUSY,
- "Addition of disk to resource already requested",
- true
- ));
+ if (removeDisk)
+ {
+ // User wants to cancel the failed add-disk operation and go back to diskless
+ // Use the existing disk removal flow to properly cleanup storage on satellite
+ errorReporter.logInfo(
+ "Toggle Disk cancel on %s/%s - cancelling failed DISK_ADD_REQUESTED, reverting to diskless",
+ nodeNameStr, rscNameStr);
+ unmarkDiskAddRequested(rsc);
+ // Also clear DISK_ADDING if it was set
+ unmarkDiskAdding(rsc);
+
+ // Set storage pool to diskless pool (overwrite the diskful pool that was set)
+ Props rscProps = ctrlPropsHelper.getProps(rsc);
+ rscProps.map().put(ApiConsts.KEY_STOR_POOL_NAME, LinStor.DISKLESS_STOR_POOL_NAME);
+
+ // Set DISK_REMOVE_REQUESTED to use the existing disk removal flow
+ // This will:
+ // 1. updateAndAdjustDisk sets DISK_REMOVING flag
+ // 2. Satellite sees DISK_REMOVING and deletes LUKS/storage devices
+ // 3. finishOperation rebuilds layer stack as diskless
+ // We keep the existing layer data so satellite can properly cleanup
+ markDiskRemoveRequested(rsc);
+
+ ctrlTransactionHelper.commit();
+
+ // Use existing disk removal flow - this will properly cleanup storage on satellite
+ return Flux
+ .<ApiCallRc>just(ApiCallRcImpl.singleApiCallRc(
+ ApiConsts.MODIFIED,
+ "Cancelling disk addition, reverting to diskless"
+ ))
+ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context))
+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition()));
+ }
+ // If adding disk and DISK_ADD_REQUESTED is already set, treat as retry
+ // 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, retrying operation",
+ nodeNameStr, rscNameStr);
+ ctrlTransactionHelper.commit();
+ return Flux
+ .<ApiCallRc>just(new ApiCallRcImpl())
+ .concatWith(updateAndAdjustDisk(nodeName, rscName, false, toggleIntoTiebreakerRef, context))
+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition()));
}
if (hasDiskRemoveRequested(rsc))
{
- }
- if (hasDiskRemoveRequested(rsc))
- {
- throw new ApiRcException(ApiCallRcImpl.simpleEntry(
- ApiConsts.FAIL_RSC_BUSY,
- "Removal of disk from resource already requested",
- true
- ));
+ if (!removeDisk)
+ {
+ // User wants to cancel the failed remove-disk operation
- }
+ ToggleDiskAction action = determineToggleDiskAction(rsc, removeDisk);
+ errorReporter.logDebug("Toggle Disk action: %s", action);
- if (!removeDisk && !ctrlVlmCrtApiHelper.isDiskless(rsc))
+ switch (action)
{
- throw new ApiRcException(ApiCallRcImpl.simpleEntry(
- ApiConsts.WARN_RSC_ALREADY_HAS_DISK,
- "Resource already has disk",
- true
- ));
- }
- if (removeDisk && ctrlVlmCrtApiHelper.isDiskless(rsc))
- {
- throw new ApiRcException(ApiCallRcImpl.simpleEntry(
- ApiConsts.WARN_RSC_ALREADY_DISKLESS,
- "Resource already diskless",
- true
- ));
+ case NOOP:
+ return handleNoopAction(rsc, removeDisk);
+ case RETRY:
+ return handleRetryAction(rsc, removeDisk, toggleIntoTiebreakerRef, context);
+ case ABORT:
+ clearToggleDiskFlags(rsc);
+ errorReporter.logInfo(
+ "Toggle Disk cancel on %s/%s - cancelling failed DISK_REMOVE_REQUESTED",
+ nodeNameStr, rscNameStr);
+ unmarkDiskRemoveRequested(rsc);
+ ctrlTransactionHelper.commit();
+ return Flux.<ApiCallRc>just(
+ ApiCallRcImpl.singleApiCallRc(
+ ApiConsts.MODIFIED,
+ "Cancelled disk removal request"
+ )
+ "Aborting previous toggle disk transition, starting new transition to %s",
+ removeDisk ? "diskless" : "diskful"
+ );
+ }
+ // If removing disk and DISK_REMOVE_REQUESTED is already set, treat as retry
+ errorReporter.logInfo(
+ "Toggle Disk retry on %s/%s - DISK_REMOVE_REQUESTED already set, continuing operation",
+ nodeNameStr, rscNameStr);
+ ctrlTransactionHelper.commit();
+ return Flux
+ .<ApiCallRc>just(new ApiCallRcImpl())
+ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context))
+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition()));
+ break;
+ case NORMAL:
+ break;
+ default:
+ throw new ImplementationError("Unhandled case: " + action);
}
if (!removeDisk && !ctrlVlmCrtApiHelper.isDiskless(rsc))
@@ -412,17 +477,43 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
true
));
}
+ ResourceDefinition rscDfn = rsc.getResourceDefinition();
+ AccessContext peerCtx = peerAccCtx.get();
+
if (removeDisk && ctrlVlmCrtApiHelper.isDiskless(rsc))
{
+ // Resource is marked as diskless - check if it has orphaned storage layers that need cleanup
+ AbsRscLayerObject<Resource> layerData = getLayerData(peerCtx, rsc);
+ if (layerData != null && (LayerUtils.hasLayer(layerData, DeviceLayerKind.LUKS) ||
+ hasNonDisklessStorageLayer(layerData)))
+ {
+ // Resource is marked as diskless but has orphaned storage layers - need cleanup
+ // Use the existing disk removal flow to properly cleanup storage on satellite
+ errorReporter.logInfo(
+ "Toggle Disk cleanup on %s/%s - resource is diskless but has orphaned storage layers, cleaning up",
+ nodeNameStr, rscNameStr);
+
+ // Set DISK_REMOVE_REQUESTED to use the existing disk removal flow
+ // This will trigger proper satellite cleanup via DISK_REMOVING flag
+ markDiskRemoveRequested(rsc);
+
+ ctrlTransactionHelper.commit();
+
+ // Use existing disk removal flow - this will properly cleanup storage on satellite
+ return Flux
+ .<ApiCallRc>just(ApiCallRcImpl.singleApiCallRc(
+ ApiConsts.MODIFIED,
+ "Cleaning up orphaned storage layers"
+ ))
+ .concatWith(updateAndAdjustDisk(nodeName, rscName, true, toggleIntoTiebreakerRef, context))
+ .concatWith(ctrlRscDfnApiCallHandler.get().updateProps(rsc.getResourceDefinition()));
+ }
throw new ApiRcException(ApiCallRcImpl.simpleEntry(
ApiConsts.WARN_RSC_ALREADY_DISKLESS,
"Resource already diskless",
true
));
}
-
- ResourceDefinition rscDfn = rsc.getResourceDefinition();
- AccessContext peerCtx = peerAccCtx.get();
if (removeDisk)
{
// Prevent removal of the last disk
@@ -1446,6 +1537,30 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
- if (removeDisk)
- {
- // Prevent removal of the last disk
- int haveDiskCount = countDisksAndIsOnline(rscDfn);
- if (haveDiskCount <= 1)
- {
- throw new ApiRcException(ApiCallRcImpl.simpleEntry(
- ApiConsts.FAIL_INSUFFICIENT_REPLICA_COUNT,
- "Cannot remove the disk from the only online resource with a disk",
- true
- ));
- }
+ validateToggleDiskPreconditions(rsc, removeDisk);
- if (!LayerUtils.hasLayer(getLayerData(peerCtx, rsc), DeviceLayerKind.DRBD))
- {
- throw new ApiRcException(ApiCallRcImpl.simpleEntry(
- ApiConsts.FAIL_INVLD_LAYER_STACK,
- "Toggle disk is only supported in combination with DRBD",
- true
- ));
- }
- }
- else
- {
- ensureAllPeersHavePeerSlotLeft(rscDfn);
- }
+ AccessContext peerCtx = peerAccCtx.get();
// Save the requested storage pool in the resource properties.
// This does not cause the storage pool to be used automatically.
@@ -628,10 +600,10 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
ctrlTransactionHelper.commit();
- String action = removeDisk ? "Removal of disk from" : "Addition of disk to";
+ String actionStr = removeDisk ? "Removal of disk from" : "Addition of disk to";
responses.addEntry(ApiCallRcImpl.simpleEntry(
ApiConsts.MODIFIED,
- action + " resource '" + rscDfn.getName().displayValue + "' " +
+ actionStr + " resource '" + rscDfn.getName().displayValue + "' " +
"on node '" + rsc.getNode().getName().displayValue + "' registered"
));
@@ -651,6 +623,40 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
);
}
+ private Flux<ApiCallRc> handleNoopAction(Resource rscRef, boolean removeDiskRef)
+ {
+ String state = removeDiskRef ? "diskless" : "diskful";
+ ApiCallRcImpl responses = new ApiCallRcImpl();
+ responses.addEntry(ApiCallRcImpl.simpleEntry(
+ ApiConsts.INFO_NOOP,
+ "Resource '" + rscRef.getResourceDefinition().getName().displayValue + "' on node '" +
+ rscRef.getNode().getName().displayValue + "' is already " + state
+ ));
+ return Flux.just(responses);
+ }
+
+ private Flux<ApiCallRc> handleRetryAction(
+ Resource rscRef,
+ boolean removeDisk,
+ boolean toggleIntoTiebreakerRef,
+ ResponseContext context
+ )
+ {
+ String direction = removeDisk ? "diskless" : "diskful";
+ ApiCallRcImpl responses = new ApiCallRcImpl();
+ NodeName nodeName = rscRef.getNode().getName();
+ ResourceName rscName = rscRef.getResourceDefinition().getName();
+ responses.addEntry(ApiCallRcImpl.simpleEntry(
+ ApiConsts.INFO_NOOP,
+ "Retrying toggle disk to " + direction + " for resource '" + rscName.displayValue +
+ "' on node '" + nodeName.displayValue + "'"
+ ));
+
+ return Flux
+ .<ApiCallRc>just(responses)
+ .concatWith(updateAndAdjustDisk(nodeName, rscName, removeDisk, toggleIntoTiebreakerRef, context));
+ }
+
private long getVlmDfnSizePrivileged(VolumeDefinition vlmDfnRef)
{
try
@@ -781,6 +787,96 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
}
}
+ private void unmarkDiskAddRequested(Resource rsc)
+ private ToggleDiskAction determineToggleDiskAction(Resource rsc, boolean removeDisk)
+ {
+ boolean isDiskless = ctrlVlmCrtApiHelper.isDiskless(rsc);
+ boolean diskAddRequested = hasDiskAddRequested(rsc);
+ boolean diskRemoveRequested = hasDiskRemoveRequested(rsc);
+
+ ToggleDiskAction action;
+ if (isDiskless)
+ {
+ if (diskAddRequested)
+ {
+ action = removeDisk ? ToggleDiskAction.ABORT : ToggleDiskAction.RETRY;
+ }
+ else if (diskRemoveRequested)
+ {
+ action = removeDisk ? ToggleDiskAction.RETRY : ToggleDiskAction.ABORT;
+ }
+ else
+ {
+ action = removeDisk ? ToggleDiskAction.NOOP : ToggleDiskAction.NORMAL;
+ }
+ }
+ else
+ {
+ if (diskRemoveRequested)
+ {
+ action = removeDisk ? ToggleDiskAction.RETRY : ToggleDiskAction.ABORT;
+ }
+ else
+ {
+ action = removeDisk ? ToggleDiskAction.NORMAL : ToggleDiskAction.NOOP;
+ }
+ }
+
+ return action;
+ }
+
+ private void validateToggleDiskPreconditions(Resource rsc, boolean removeDisk)
+ {
+ ResourceDefinition rscDfn = rsc.getResourceDefinition();
+ if (removeDisk)
+ {
+ validateDiskRemovalAllowed(rsc, rscDfn);
+ }
+ else
+ {
+ ensureAllPeersHavePeerSlotLeft(rscDfn);
+ }
+ }
+
+ private void clearToggleDiskFlags(Resource rsc)
+ {
+ try
+ {
+ rsc.getStateFlags().disableFlags(apiCtx, Resource.Flags.DISK_ADD_REQUESTED);
+ rsc.getStateFlags().disableFlags(
+ apiCtx,
+ Resource.Flags.DISK_ADD_REQUESTED,
+ Resource.Flags.DISK_ADDING,
+ Resource.Flags.DISK_REMOVE_REQUESTED,
+ Resource.Flags.DISK_REMOVING
+ );
+ }
+ catch (AccessDeniedException | DatabaseException exc)
+ {
@ -170,60 +234,28 @@ index d93a18014..cc8ce4f04 100644
+ }
+ }
+
+ private void unmarkDiskRemoveRequested(Resource rsc)
+ private void validateDiskRemovalAllowed(Resource rsc, ResourceDefinition rscDfn)
+ {
+ try
+ int haveDiskCount = countDisksAndIsOnline(rscDfn);
+ if (haveDiskCount <= 1)
+ {
+ rsc.getStateFlags().disableFlags(apiCtx, Resource.Flags.DISK_REMOVE_REQUESTED);
+ throw new ApiRcException(ApiCallRcImpl.simpleEntry(
+ ApiConsts.FAIL_INSUFFICIENT_REPLICA_COUNT,
+ "Cannot remove the disk from the only online resource with a disk",
+ true
+ ));
+ }
+ catch (AccessDeniedException | DatabaseException exc)
+
+ if (!LayerUtils.hasLayer(getLayerData(peerAccCtx.get(), rsc), DeviceLayerKind.DRBD))
+ {
+ throw new ImplementationError(exc);
+ throw new ApiRcException(ApiCallRcImpl.simpleEntry(
+ ApiConsts.FAIL_INVLD_LAYER_STACK,
+ "Toggle disk is only supported in combination with DRBD",
+ true
+ ));
+ }
+ }
+
private void markDiskAdded(Resource rscData)
{
try
@@ -1511,6 +1626,41 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
return layerData;
}
+ /**
+ * Check if the layer stack has a non-diskless STORAGE layer.
+ * This is used to detect orphaned storage layers that need cleanup.
+ */
+ private boolean hasNonDisklessStorageLayer(AbsRscLayerObject<Resource> layerDataRef)
+ {
+ boolean hasNonDiskless = false;
+ if (layerDataRef != null)
+ {
+ if (layerDataRef.getLayerKind() == DeviceLayerKind.STORAGE)
+ {
+ for (VlmProviderObject<Resource> vlmData : layerDataRef.getVlmLayerObjects().values())
+ {
+ if (vlmData.getProviderKind() != DeviceProviderKind.DISKLESS)
+ {
+ hasNonDiskless = true;
+ break;
+ }
+ }
+ }
+ if (!hasNonDiskless)
+ {
+ for (AbsRscLayerObject<Resource> child : layerDataRef.getChildren())
+ {
+ if (hasNonDisklessStorageLayer(child))
+ {
+ hasNonDiskless = true;
+ break;
+ }
+ }
+ }
+ }
+ return hasNonDiskless;
+ }
+
private LockGuard createLockGuard()
{
return lockGuardFactory.buildDeferred(LockType.WRITE, LockObj.NODES_MAP, LockObj.RSC_DFN_MAP);
/**
* Although we need to rebuild the layerData as the layerList might have changed, if we do not
* deactivate (i.e. down) the current resource, we need to make sure that deleting DrbdRscData

View file

@ -1,69 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Andrei Kvapil <kvapss@gmail.com>
Date: Fri, 28 Mar 2026 13:00:00 +0100
Subject: [PATCH] fix(drbd): preserve TCP ports during toggle-disk operations
Prevent TCP port mismatches after toggle-disk operations by preserving
existing TCP ports when rebuilding DrbdRscData.
Root Cause:
-----------
During toggle-disk operations, removeLayerData() deletes DrbdRscData
(freeing its TCP ports from the number pool), then ensureStackDataExists()
creates new DrbdRscData. Since the payload has no explicit tcpPorts,
the controller allocates new ports from the pool -- which may differ from
the old ports if other resources claimed them in the meantime.
The controller correctly avoids collisions in its own number pool, but
the satellite may miss the update (e.g. during controller restart or
network issues). When this happens, the satellite keeps the old ports
while peers receive the new ones, causing DRBD connection failures
(StandAlone/Connecting state).
Additionally, remove the redundant ensureStackDataExists() call from
resetStoragePools() -- the caller already invokes it with the correct
payload.
Solution:
---------
1. Add copyDrbdTcpPortsIfExists() to save existing TCP ports into the
LayerPayload before removeLayerData() deletes them.
2. Call it from copyDrbdNodeIdIfExists() (covers both toggle-disk paths)
and from the needsDeactivate path (shared storage pool case).
3. Remove the redundant ensureStackDataExists() from resetStoragePools().
This ensures the same TCP ports are reused when DrbdRscData is recreated,
eliminating the window for port mismatch between controller and satellites.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
---
.../controller/CtrlRscToggleDiskApiCallHandler.java | 40 +++++++++++++++++++--
.../linstor/layer/resource/CtrlRscLayerDataFactory.java | 2 --
2 files changed, 38 insertions(+), 4 deletions(-)
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 ccdb0cee5..b0554c2ec 100644
index d93a18014..01cfbbacf 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,6 +58,7 @@ 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;
@@ -37,6 +37,7 @@ import com.linbit.linstor.core.objects.StorPool;
import com.linbit.linstor.core.objects.Volume;
import com.linbit.linstor.core.objects.VolumeDefinition;
import com.linbit.linstor.core.objects.utils.MixedStorPoolHelper;
+import com.linbit.linstor.core.types.TcpPortNumber;
import com.linbit.linstor.storage.interfaces.categories.resource.VlmProviderObject;
import com.linbit.linstor.storage.kinds.DeviceLayerKind;
import com.linbit.linstor.storage.kinds.DeviceProviderKind;
@@ -88,6 +89,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import com.linbit.linstor.dbdrivers.DatabaseException;
import com.linbit.linstor.event.EventWaiter;
import com.linbit.linstor.event.ObjectIdentifier;
@@ -85,6 +86,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.TreeSet;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -587,8 +589,9 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
@@ -575,12 +577,13 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
/*
* We also have to remove the currently diskless DrbdRscData and free up the node-id as now we must
- * use the shared resource's node-id
@ -73,30 +29,39 @@ index ccdb0cee5..b0554c2ec 100644
}
else
{
@@ -726,7 +729,7 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
- copyDrbdNodeIdIfExists(rsc, payload);
+ copyDrbdSettings(rsc, payload);
}
/*
* rebuilds the layerdata in case we just removed it..
@@ -782,10 +785,20 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
}
/**
* Although we need to rebuild the layerData as the layerList might have changed, if we do not
* deactivate (i.e. down) the current resource, we need to make sure that deleting DrbdRscData
- * Although we need to rebuild the layerData as the layerList might have changed, if we do not
- * deactivate (i.e. down) the current resource, we need to make sure that deleting DrbdRscData
- * and recreating a new DrbdRscData ends up with the same node-id as before.
+ * and recreating a new DrbdRscData ends up with the same node-id and TCP ports as before.
+ * Copies DRBD settings (node-id and TCP ports) from the existing DrbdRscData into the payload
+ * before removeLayerData() deletes them. This ensures that recreated DrbdRscData ends up with
+ * the same node-id and TCP ports as before.
+ *
+ * TCP ports must be preserved because if the satellite misses the update (e.g. due to controller
+ * restart or connectivity issues), it will keep the old ports while peers receive the new ones,
+ * causing DRBD connections to fail with StandAlone state.
*/
private void copyDrbdNodeIdIfExists(Resource rsc, LayerPayload payload) throws ImplementationError
{
@@ -743,6 +746,37 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
DrbdRscData<Resource> drbdRscData = (DrbdRscData<Resource>) drbdRscDataSet.iterator().next();
payload.drbdRsc.nodeId = drbdRscData.getNodeId().value;
}
+ private void copyDrbdSettings(Resource rsc, LayerPayload payload) throws ImplementationError
+ {
+ copyDrbdNodeIdIfExists(rsc, payload);
+ copyDrbdTcpPortsIfExists(rsc, payload);
+ }
+
+ /**
+ * Preserves existing TCP ports during toggle-disk operations.
+ *
+ * When removeLayerData() deletes DrbdRscData, the TCP ports are freed from the number pool.
+ * If ensureStackDataExists() then allocates different ports, and the satellite misses the update
+ * (e.g. due to controller restart or connectivity issues), the satellite keeps the old ports
+ * while peers get the new ones, causing DRBD connections to fail with StandAlone state.
+ */
private void copyDrbdNodeIdIfExists(Resource rsc, LayerPayload payload) throws ImplementationError
{
Set<AbsRscLayerObject<Resource>> drbdRscDataSet = LayerRscUtils.getRscDataByLayer(
@@ -804,6 +817,28 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
}
}
+ private void copyDrbdTcpPortsIfExists(Resource rsc, LayerPayload payload) throws ImplementationError
+ {
+ Set<AbsRscLayerObject<Resource>> drbdRscDataSet = LayerRscUtils.getRscDataByLayer(
@ -117,22 +82,56 @@ index ccdb0cee5..b0554c2ec 100644
+ payload.drbdRsc.tcpPorts = portInts;
+ }
+ }
}
+ }
+
private List<DeviceLayerKind> removeLayerData(Resource rscRef)
{
List<DeviceLayerKind> layerList;
@@ -1058,15 +1093,15 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
/*
* We also have to remove the possible meta-children of previous StorageRscData.
* LayerData will be recreated with ensureStackDataExists.
- * However, we still need to remember our node-id if we had / have DRBD in the list
+ * However, we still need to remember our DRBD settings if we had / have DRBD in the list
*/
- copyDrbdNodeIdIfExists(rsc, payload);
+ copyDrbdSettings(rsc, payload);
layerList = removeLayerData(rsc);
}
else
{
markDiskAdded(rsc);
- ctrlLayerStackHelper.resetStoragePools(rsc);
+ ctrlLayerStackHelper.resetStoragePools(rsc, false);
}
ctrlLayerStackHelper.ensureStackDataExists(rsc, layerList, payload);
diff --git a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
index 3538b380c..4f589145e 100644
index 3538b380c..f9733b6f1 100644
--- a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
+++ b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
@@ -276,8 +276,6 @@ public class CtrlRscLayerDataFactory
@@ -263,6 +263,11 @@ public class CtrlRscLayerDataFactory
}
public void resetStoragePools(Resource rscRef)
+ {
+ resetStoragePools(rscRef, true);
+ }
+
+ public void resetStoragePools(Resource rscRef, boolean callEnsureStackDataExistsRef)
{
try
{
@@ -276,8 +281,10 @@ public class CtrlRscLayerDataFactory
rscDataToProcess.addAll(rscData.getChildren());
}
-
- ensureStackDataExists(rscRef, null, new LayerPayload());
+ if (callEnsureStackDataExistsRef)
+ {
+ ensureStackDataExists(rscRef, null, new LayerPayload());
+ }
}
catch (AccessDeniedException exc)
{
--
2.39.5 (Apple Git-154)

View file

@ -0,0 +1,570 @@
diff --git a/satellite/src/main/java/com/linbit/linstor/layer/storage/AbsStorageProvider.java b/satellite/src/main/java/com/linbit/linstor/layer/storage/AbsStorageProvider.java
--- a/satellite/src/main/java/com/linbit/linstor/layer/storage/AbsStorageProvider.java
+++ b/satellite/src/main/java/com/linbit/linstor/layer/storage/AbsStorageProvider.java
@@ -2081,7 +2081,6 @@ public abstract class AbsStorageProvider<
)
throws IOException, AccessDeniedException
{
- final StorPoolName storPoolObjName = storPoolObj.getName();
if (storDevicePath != null)
{
errorReporter.logDebug("updateMinIoSize: Have storDevicePath \"%s\"", storDevicePath);
@@ -2094,35 +2093,18 @@ public abstract class AbsStorageProvider<
"updateMinIoSize: Block device path is \"%s\"",
blockDevicePath.toString()
);
- final long minIoSize = BlockSizeInfo.getBlockSize(blockDevicePath);
-
- boolean updateValue = true;
-
- final String propKey = StorageConstants.NAMESPACE_INTERNAL + '/' +
- StorageConstants.BLK_DEV_MIN_IO_SIZE;
- final Props storPoolProps = storPoolObj.getProps(storDriverAccCtx);
- final @Nullable String currentPropValue = storPoolProps.getProp(propKey);
- if (currentPropValue != null)
- {
- try
- {
- final long currentPropMinIoSize = Long.parseLong(currentPropValue);
- updateValue = currentPropMinIoSize != minIoSize;
- }
- catch (NumberFormatException ignored)
- {
- }
- }
-
- if (updateValue)
- {
- final String propValue = Long.toString(minIoSize);
- errorReporter.logDebug(
- "Storage pool \"%s\": Set property \"%s\" = \"%s\"",
- storPoolObjName.displayValue, propKey, propValue
- );
- propsChange.changeStorPoolProp(storPoolObj, propKey, propValue);
- }
+ updatePropIfNeeded(
+ propsChange,
+ storPoolObj,
+ StorageConstants.NAMESPACE_INTERNAL + '/' + StorageConstants.BLK_DEV_MIN_IO_SIZE,
+ Long.toString(BlockSizeInfo.getBlockSize(blockDevicePath))
+ );
+ updatePropIfNeeded(
+ propsChange,
+ storPoolObj,
+ StorageConstants.NAMESPACE_INTERNAL + '/' + StorageConstants.BLK_DEV_OPT_IO_SIZE,
+ Long.toString(BlockSizeInfo.getOptimalIoSize(blockDevicePath))
+ );
}
else
{
@@ -2130,6 +2112,28 @@ public abstract class AbsStorageProvider<
}
}
+ private void updatePropIfNeeded(
+ LocalPropsChangePojo propsChangeRef,
+ StorPool storPoolObjRef,
+ String propKeyRef,
+ String propValueRef
+ )
+ throws AccessDeniedException
+ {
+ final Props storPoolProps = storPoolObjRef.getProps(storDriverAccCtx);
+ final @Nullable String currentPropValue = storPoolProps.getProp(propKeyRef);
+ if (currentPropValue == null || !currentPropValue.equals(propValueRef))
+ {
+ errorReporter.logDebug(
+ "Storage pool \"%s\": Set property \"%s\" = \"%s\"",
+ storPoolObjRef.getName().displayValue,
+ propKeyRef,
+ propValueRef
+ );
+ propsChangeRef.changeStorPoolProp(storPoolObjRef, propKeyRef, propValueRef);
+ }
+ }
+
@SuppressWarnings("unchecked")
@Override
public void updateAllocatedSize(VlmProviderObject<Resource> vlmDataRef)
diff --git a/satellite/src/main/java/com/linbit/linstor/layer/storage/utils/BlockSizeInfo.java b/satellite/src/main/java/com/linbit/linstor/layer/storage/utils/BlockSizeInfo.java
--- a/satellite/src/main/java/com/linbit/linstor/layer/storage/utils/BlockSizeInfo.java
+++ b/satellite/src/main/java/com/linbit/linstor/layer/storage/utils/BlockSizeInfo.java
@@ -3,8 +3,11 @@ package com.linbit.linstor.layer.storage.utils;
import com.linbit.utils.MathUtils;
import com.linbit.utils.SymbolicLinkResolver;
+import static com.linbit.linstor.layer.storage.BlockSizeConsts.DFLT_OPT_IO_SIZE;
import static com.linbit.linstor.layer.storage.BlockSizeConsts.DFLT_IO_SIZE;
+import static com.linbit.linstor.layer.storage.BlockSizeConsts.MAX_OPT_IO_SIZE;
import static com.linbit.linstor.layer.storage.BlockSizeConsts.MAX_IO_SIZE;
+import static com.linbit.linstor.layer.storage.BlockSizeConsts.MIN_OPT_IO_SIZE;
import static com.linbit.linstor.layer.storage.BlockSizeConsts.MIN_IO_SIZE;
import java.io.FileInputStream;
@@ -13,6 +16,8 @@ import java.nio.file.Path;
public class BlockSizeInfo
{
+ private static final int NUMBER_BUFFER_SIZE = 32;
+
/**
* Determines the blocksize, aka minimum I/O size, for the specified backing storage path.
*
@@ -51,7 +56,7 @@ public class BlockSizeInfo
final Path infoSourceName = blockDevice.getFileName();
final Path infoSource = Path.of("/sys/block", infoSourceName.toString(), "queue/physical_block_size");
- final byte[] data = new byte[32];
+ final byte[] data = new byte[NUMBER_BUFFER_SIZE];
try (final FileInputStream fileIn = new FileInputStream(infoSource.toString()))
{
final int readCount = fileIn.read(data);
@@ -75,4 +80,38 @@ public class BlockSizeInfo
}
return blockSize;
}
+
+ public static long getOptimalIoSize(final Path storageObj)
+ {
+ long optIoSize = DFLT_OPT_IO_SIZE;
+ try
+ {
+ final Path blockDevice = SymbolicLinkResolver.resolveSymLink(storageObj);
+ final Path infoSourceName = blockDevice.getFileName();
+ final Path infoSource = Path.of("/sys/block", infoSourceName.toString(), "queue/optimal_io_size");
+
+ final byte[] data = new byte[NUMBER_BUFFER_SIZE];
+ try (final FileInputStream fileIn = new FileInputStream(infoSource.toString()))
+ {
+ final int readCount = fileIn.read(data);
+ if (readCount > 0)
+ {
+ String numberStr = new String(data, 0, readCount);
+ numberStr = numberStr.trim();
+ try
+ {
+ final long unboundedOptIoSize = Long.parseLong(numberStr);
+ optIoSize = MathUtils.bounds(MIN_OPT_IO_SIZE, unboundedOptIoSize, MAX_OPT_IO_SIZE);
+ }
+ catch (NumberFormatException ignored)
+ {
+ }
+ }
+ }
+ }
+ catch (IOException ignored)
+ {
+ }
+ return optIoSize;
+ }
}
diff --git a/server/src/main/java/com/linbit/SizeConv.java b/server/src/main/java/com/linbit/SizeConv.java
--- a/server/src/main/java/com/linbit/SizeConv.java
+++ b/server/src/main/java/com/linbit/SizeConv.java
@@ -16,6 +16,7 @@ public class SizeConv
public enum SizeUnit
{
UNIT_B,
+ UNIT_SECTORS,
UNIT_KiB,
UNIT_MiB,
UNIT_GiB,
@@ -41,6 +42,9 @@ public class SizeConv
case UNIT_B:
factor = FACTOR_B;
break;
+ case UNIT_SECTORS:
+ factor = FACTOR_SECTORS;
+ break;
case UNIT_KiB:
factor = FACTOR_KiB;
break;
@@ -111,6 +115,9 @@ public class SizeConv
case "b":
unit = SizeUnit.UNIT_B;
break;
+ case "s":
+ unit = SizeUnit.UNIT_SECTORS;
+ break;
case "k":
// fall-through
case "kb":
@@ -217,6 +224,9 @@ public class SizeConv
// Factor 1
public static final BigInteger FACTOR_B = BigInteger.valueOf(1L);
+ // Factor 512
+ public static final BigInteger FACTOR_SECTORS = BigInteger.valueOf(512L);
+
// Factor 1,024
// Naming convention exception: SI unit capitalization rules
@SuppressWarnings("checkstyle:constantname")
diff --git a/server/src/main/java/com/linbit/linstor/layer/luks/LuksLayerSizeCalculator.java b/server/src/main/java/com/linbit/linstor/layer/luks/LuksLayerSizeCalculator.java
--- a/server/src/main/java/com/linbit/linstor/layer/luks/LuksLayerSizeCalculator.java
+++ b/server/src/main/java/com/linbit/linstor/layer/luks/LuksLayerSizeCalculator.java
@@ -1,27 +1,62 @@
package com.linbit.linstor.layer.luks;
+import com.linbit.SizeConv;
+import com.linbit.SizeConv.SizeUnit;
import com.linbit.exceptions.InvalidSizeException;
+import com.linbit.linstor.PriorityProps;
+import com.linbit.linstor.annotation.Nullable;
+import com.linbit.linstor.api.ApiConsts;
+import com.linbit.linstor.core.objects.AbsResource;
+import com.linbit.linstor.core.objects.AbsVolume;
+import com.linbit.linstor.core.objects.Resource;
+import com.linbit.linstor.core.objects.ResourceDefinition;
+import com.linbit.linstor.core.objects.ResourceGroup;
+import com.linbit.linstor.core.objects.Snapshot;
+import com.linbit.linstor.core.objects.SnapshotVolume;
+import com.linbit.linstor.core.objects.StorPool;
+import com.linbit.linstor.core.objects.Volume;
+import com.linbit.linstor.core.objects.VolumeDefinition;
import com.linbit.linstor.dbdrivers.DatabaseException;
import com.linbit.linstor.layer.AbsLayerSizeCalculator;
+import com.linbit.linstor.netcom.Peer;
+import com.linbit.linstor.propscon.InvalidKeyException;
+import com.linbit.linstor.propscon.ReadOnlyProps;
import com.linbit.linstor.security.AccessDeniedException;
+import com.linbit.linstor.storage.StorageConstants;
import com.linbit.linstor.storage.data.adapter.luks.LuksVlmData;
import com.linbit.linstor.storage.interfaces.categories.resource.VlmProviderObject;
import com.linbit.linstor.storage.kinds.DeviceLayerKind;
import com.linbit.linstor.storage.kinds.ExtTools;
import com.linbit.linstor.storage.kinds.ExtToolsInfo;
import com.linbit.linstor.storage.kinds.ExtToolsInfo.Version;
+import com.linbit.linstor.utils.layer.LayerVlmUtils;
+import com.linbit.utils.ShellUtils;
+import com.linbit.utils.SignedAlign;
import javax.inject.Inject;
import javax.inject.Singleton;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
@Singleton
public class LuksLayerSizeCalculator extends AbsLayerSizeCalculator<LuksVlmData<?>>
{
+ public static final String LUKS2_OPT_METADATA_SIZE = "--luks2-metadata-size";
+ public static final String LUKS2_OPT_KEYSLOTS_SIZE = "--luks2-keyslots-size";
+ public static final String LUKS2_OPT_OFFSET = "--offset";
+ private static final String LUKS2_OPT_ALIGN_PAYLOAD = "--align-payload";
+ private static final long DFLT_LUKS2_METADATA_SIZE_IN_BYTES = 16L << 10;
+ private static final long DFLT_ALIGNMENT_1MIB_IN_BYTES = 1L << 20;
+ private static final long LUKS1_HEADER_SIZE_IN_KIB = 2L << 10;
+ private static final long LUKS2_HEADER_SIZE_IN_KIB = 16L << 10;
+ private static final long LUKS2_HEADER_SIZE_IN_BYTES = LUKS2_HEADER_SIZE_IN_KIB << 10;
- // linstor calculates in KiB
- private static final int MIB = 1024;
- private static final int LUKS1_HEADER_SIZE = 2 * MIB;
- private static final int LUKS2_HEADER_SIZE = 16 * MIB;
+ private static final Pattern PATTERN_SIZE = Pattern.compile("(\\d+)([kKmMgGtT]i?[bB]?|[sS]|)");
@Inject
public LuksLayerSizeCalculator(AbsLayerSizeCalculatorInit initRef)
@@ -74,30 +109,266 @@ public class LuksLayerSizeCalculator extends AbsLayerSizeCalculator<LuksVlmData<
}
private long getLuksHeaderSize(VlmProviderObject<?> vlmDataRef)
- throws AccessDeniedException
+ throws AccessDeniedException, InvalidSizeException
{
- ExtToolsInfo cryptSetupInfo = vlmDataRef.getRscLayerObject()
+ @Nullable Peer peer = vlmDataRef.getRscLayerObject()
.getAbsResource()
.getNode()
- .getPeer(sysCtx)
- .getExtToolsManager()
+ .getPeer(sysCtx);
+ if (peer == null)
+ {
+ throw new InvalidSizeException(
+ "Could not calculate size of LUKS volume, since cryptsetup's version could not be determined",
+ null
+ );
+ }
+ ExtToolsInfo cryptSetupInfo = peer.getExtToolsManager()
.getExtToolInfo(ExtTools.CRYPT_SETUP);
- long luksHeaderSize;
+
+ final long luksHeaderSize;
if (cryptSetupInfo != null && cryptSetupInfo.isSupported())
{
if (cryptSetupInfo.hasVersionOrHigher(new Version(2, 1)))
{
- luksHeaderSize = LUKS2_HEADER_SIZE;
+ luksHeaderSize = calcLuks2HeaderSize(vlmDataRef);
}
else
{
- luksHeaderSize = LUKS1_HEADER_SIZE;
+ luksHeaderSize = LUKS1_HEADER_SIZE_IN_KIB;
}
}
else
{
- luksHeaderSize = -1;
+ throw new InvalidSizeException(
+ "Could not calculate size of LUKS volume, since cryptsetup's version could not be determined",
+ null
+ );
}
return luksHeaderSize;
}
+
+ private long calcLuks2HeaderSize(VlmProviderObject<?> vlmDataRef) throws AccessDeniedException
+ {
+ PriorityProps prioProps = getPrioProps(vlmDataRef);
+
+ final @Nullable String userOptProp = prioProps.getProp(
+ ApiConsts.KEY_STOR_DRIVER_LUKS_FORMAT_OPTIONS,
+ ApiConsts.NAMESPC_STORAGE_DRIVER
+ );
+ List<String> userOptions = userOptProp != null ?
+ ShellUtils.shellSplit(userOptProp) :
+ new LinkedList<>();
+
+ final long alignedHeaderSizeInBytes;
+ final long unalignedHeaderSizeInBytes;
+ @Nullable Long cryptsetupOffsetInBytes = getLongOptionValue(
+ userOptions,
+ LUKS2_OPT_OFFSET,
+ SizeUnit.UNIT_SECTORS
+ );
+ if (cryptsetupOffsetInBytes != null)
+ {
+ alignedHeaderSizeInBytes = cryptsetupOffsetInBytes;
+ }
+ else
+ {
+ @Nullable Long cryptsetupLuks2KeyslotsSize = getLongOptionValue(
+ userOptions,
+ LUKS2_OPT_KEYSLOTS_SIZE
+ );
+ if (cryptsetupLuks2KeyslotsSize == null)
+ {
+ unalignedHeaderSizeInBytes = LUKS2_HEADER_SIZE_IN_BYTES;
+ }
+ else
+ {
+ @Nullable Long cryptsetupLuks2MetadataSize = getLongOptionValue(
+ userOptions,
+ LUKS2_OPT_METADATA_SIZE
+ );
+ cryptsetupLuks2MetadataSize = cryptsetupLuks2MetadataSize == null ?
+ DFLT_LUKS2_METADATA_SIZE_IN_BYTES :
+ cryptsetupLuks2MetadataSize;
+
+ unalignedHeaderSizeInBytes = 2 * cryptsetupLuks2MetadataSize + cryptsetupLuks2KeyslotsSize;
+ }
+
+ long alignment = getAlignment(vlmDataRef, userOptions);
+ alignedHeaderSizeInBytes = new SignedAlign(alignment).ceiling(unalignedHeaderSizeInBytes);
+ }
+ return SizeConv.convert(
+ alignedHeaderSizeInBytes,
+ SizeUnit.UNIT_B,
+ SizeUnit.UNIT_KiB
+ );
+ }
+
+ private long getAlignment(VlmProviderObject<?> vlmDataRef, List<String> userOptions) throws AccessDeniedException
+ {
+ @Nullable Long cryptsetupAlignPayloadInBytes = getLongOptionValue(
+ userOptions,
+ LUKS2_OPT_ALIGN_PAYLOAD,
+ SizeUnit.UNIT_SECTORS
+ );
+
+ long alignment = DFLT_ALIGNMENT_1MIB_IN_BYTES;
+ if (cryptsetupAlignPayloadInBytes != null)
+ {
+ alignment = cryptsetupAlignPayloadInBytes;
+ }
+ else
+ {
+ final long maxOptIoSize = getMaxOptIoSize(vlmDataRef);
+ alignment = Math.max(alignment, maxOptIoSize);
+ }
+ return alignment;
+ }
+
+ private long getMaxOptIoSize(VlmProviderObject<?> vlmDataRef) throws InvalidKeyException, AccessDeniedException
+ {
+ long ret = 0;
+ Set<StorPool> storPoolSet = LayerVlmUtils.getStorPoolSet(vlmDataRef, sysCtx);
+ for (StorPool sp : storPoolSet)
+ {
+ @Nullable String strValue = sp.getProps(sysCtx)
+ .getProp(
+ StorageConstants.BLK_DEV_OPT_IO_SIZE,
+ StorageConstants.NAMESPACE_INTERNAL
+ );
+ if (strValue != null)
+ {
+ try
+ {
+ long parsed = Long.parseLong(strValue);
+ ret = Math.max(parsed, ret);
+ }
+ catch (NumberFormatException ignored)
+ {
+ errorReporter.logWarning(
+ "LuksHeaderSize: Failed to parse '%s' from prop %s. Defaulting to 0 opt_io_size " +
+ "(no recommendation/hint)",
+ strValue,
+ StorageConstants.NAMESPACE_INTERNAL + "/" + StorageConstants.BLK_DEV_OPT_IO_SIZE
+ );
+ }
+ }
+ }
+ return ret;
+ }
+
+ private @Nullable Long getLongOptionValue(List<String> userOptionsRef, String optRef)
+ {
+ return getLongOptionValue(userOptionsRef, optRef, SizeUnit.UNIT_B);
+ }
+
+ @SuppressWarnings("checkstyle:magicnumber")
+ private @Nullable Long getLongOptionValue(List<String> userOptionsRef, String optRef, SizeUnit dfltSizeUnit)
+ {
+ @Nullable Long ret = null;
+ @Nullable String val = findLastValue(userOptionsRef, optRef);
+ if (val != null && !val.isBlank())
+ {
+ Matcher matcher = PATTERN_SIZE.matcher(val);
+ if (matcher.matches())
+ {
+ try
+ {
+ ret = Long.parseLong(matcher.group(1));
+ String unit = matcher.group(2);
+ SizeUnit sizeUnit;
+ if (unit.isBlank())
+ {
+ sizeUnit = dfltSizeUnit;
+ }
+ else
+ {
+ boolean forcePowerOfTwo = unit.length() == 1 || unit.length() == 3;
+ sizeUnit = SizeUnit.parse(unit, forcePowerOfTwo);
+ }
+
+ ret = SizeConv.convert(ret, sizeUnit, SizeUnit.UNIT_B);
+ }
+ catch (NumberFormatException ignored)
+ {
+ errorReporter.logWarning(
+ "LuksHeaderSize: Failed to parse '%s' from option '%s %s'.",
+ matcher.group(1),
+ optRef,
+ val
+ );
+ }
+ }
+ }
+ return ret;
+ }
+
+ private @Nullable String findLastValue(List<String> userOptionsRef, String optRef)
+ {
+ Iterator<String> it = userOptionsRef.iterator();
+ @Nullable String val = null;
+ while (it.hasNext())
+ {
+ String opt = it.next();
+ if (opt.equals(optRef))
+ {
+ if (it.hasNext())
+ {
+ val = it.next();
+ }
+ else
+ {
+ val = null;
+ }
+ }
+ else if (opt.startsWith(optRef + "="))
+ {
+ val = opt.substring(optRef.length() + 1);
+ if (val.isBlank())
+ {
+ val = null;
+ }
+ }
+ }
+ return val;
+ }
+
+ private PriorityProps getPrioProps(VlmProviderObject<?> vlmDataRef) throws AccessDeniedException
+ {
+ final AbsVolume<?> vlm = vlmDataRef.getVolume();
+ final AbsResource<?> rsc = vlm.getAbsResource();
+ final ResourceDefinition rscDfn = vlm.getResourceDefinition();
+ final VolumeDefinition vlmDfn = vlm.getVolumeDefinition();
+ final ResourceGroup rscGrp = rscDfn.getResourceGroup();
+
+ final ReadOnlyProps vlmProps;
+ final ReadOnlyProps rscProps;
+ if (vlm instanceof Volume)
+ {
+ vlmProps = ((Volume) vlm).getProps(sysCtx);
+ rscProps = ((Resource) rsc).getProps(sysCtx);
+ }
+ else
+ {
+ vlmProps = ((SnapshotVolume) vlm).getVlmProps(sysCtx);
+ rscProps = ((Snapshot) rsc).getRscProps(sysCtx);
+ }
+
+ final PriorityProps prioProps = new PriorityProps(
+ vlmProps,
+ rscProps
+ );
+ for (StorPool storPool : LayerVlmUtils.getStorPoolSet(vlmDataRef, sysCtx))
+ {
+ prioProps.addProps(storPool.getProps(sysCtx));
+ }
+ prioProps.addProps(
+ rsc.getNode().getProps(sysCtx),
+ vlmDfn.getProps(sysCtx),
+ rscDfn.getProps(sysCtx),
+ rscGrp.getVolumeGroupProps(sysCtx, vlmDfn.getVolumeNumber()),
+ rscGrp.getProps(sysCtx),
+ stltProps
+ );
+ return prioProps;
+ }
}
diff --git a/server/src/main/java/com/linbit/linstor/layer/storage/BlockSizeConsts.java b/server/src/main/java/com/linbit/linstor/layer/storage/BlockSizeConsts.java
--- a/server/src/main/java/com/linbit/linstor/layer/storage/BlockSizeConsts.java
+++ b/server/src/main/java/com/linbit/linstor/layer/storage/BlockSizeConsts.java
@@ -13,4 +13,9 @@ public class BlockSizeConsts
// Default value for the minimum_io_size value of non-storage layers
public static final long DFLT_SPECIAL_IO_SIZE = (1L << 12);
+
+ // optimal_io_size may be 0 to indicate no recommendation.
+ public static final long MIN_OPT_IO_SIZE = 0;
+ public static final long DFLT_OPT_IO_SIZE = 0;
+ public static final long MAX_OPT_IO_SIZE = Long.MAX_VALUE;
}
diff --git a/server/src/main/java/com/linbit/linstor/storage/StorageConstants.java b/server/src/main/java/com/linbit/linstor/storage/StorageConstants.java
--- a/server/src/main/java/com/linbit/linstor/storage/StorageConstants.java
+++ b/server/src/main/java/com/linbit/linstor/storage/StorageConstants.java
@@ -11,6 +11,7 @@ public class StorageConstants
public static final String NAMESPACE_NVME = ApiConsts.NAMESPC_STORAGE_DRIVER + "/NVME";
public static final String NAMESPACE_INTERNAL = NAMESPACE_STOR_DRIVER + "/internal/";
+ public static final String BLK_DEV_OPT_IO_SIZE = "optIoSize";
public static final String BLK_DEV_MIN_IO_SIZE = "minIoSize";
public static final String BLK_DEV_MIN_IO_SIZE_AUTO = "minIoSizeAuto";
public static final String BLK_DEV_MAX_BIO_SIZE = "maxBioSize";

View file

@ -1,63 +0,0 @@
diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java
index a302ee835..01967a31f 100644
--- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java
+++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java
@@ -371,10 +371,13 @@ public class DrbdLayer implements DeviceLayer
boolean isDiskless = drbdRscData.getAbsResource().isDrbdDiskless(workerCtx);
StateFlags<Flags> rscFlags = drbdRscData.getAbsResource().getStateFlags();
boolean isDiskRemoving = rscFlags.isSet(workerCtx, Resource.Flags.DISK_REMOVING);
+ // Check if we're in toggle-disk operation (adding disk to diskless resource)
+ boolean isDiskAdding = rscFlags.isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING);
boolean contProcess = isDiskless;
- boolean processChildren = !isDiskless || isDiskRemoving;
+ // Process children when: has disk, removing disk, OR adding disk (toggle-disk)
+ boolean processChildren = !isDiskless || isDiskRemoving || isDiskAdding;
// do not process children when ONLY DRBD_DELETE flag is set (DELETE flag is still unset)
processChildren &= (!rscFlags.isSet(workerCtx, Resource.Flags.DRBD_DELETE) ||
rscFlags.isSet(workerCtx, Resource.Flags.DELETE));
@@ -570,7 +573,11 @@ public class DrbdLayer implements DeviceLayer
{
// hasMetaData needs to be run after child-resource processed
List<DrbdVlmData<Resource>> createMetaData = new ArrayList<>();
- if (!drbdRscData.getAbsResource().isDrbdDiskless(workerCtx) && !skipDisk)
+ // Check if we're in toggle-disk operation (adding disk to diskless resource)
+ boolean isDiskAddingForMd = drbdRscData.getAbsResource().getStateFlags()
+ .isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING);
+ // Create metadata when: has disk OR adding disk (toggle-disk), and skipDisk is disabled
+ if ((!drbdRscData.getAbsResource().isDrbdDiskless(workerCtx) || isDiskAddingForMd) && !skipDisk)
{
// do not try to create meta data while the resource is diskless or skipDisk is enabled
for (DrbdVlmData<Resource> drbdVlmData : checkMetaData)
@@ -988,8 +995,10 @@ public class DrbdLayer implements DeviceLayer
{
List<DrbdVlmData<Resource>> checkMetaData = new ArrayList<>();
Resource rsc = drbdRscData.getAbsResource();
+ // Include DISK_ADD_REQUESTED/DISK_ADDING for toggle-disk scenario where we need to check/create metadata
if (!rsc.isDrbdDiskless(workerCtx) ||
- rsc.getStateFlags().isSet(workerCtx, Resource.Flags.DISK_REMOVING)
+ rsc.getStateFlags().isSet(workerCtx, Resource.Flags.DISK_REMOVING) ||
+ rsc.getStateFlags().isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING)
)
{
// using a dedicated list to prevent concurrentModificationException
@@ -1177,9 +1186,16 @@ public class DrbdLayer implements DeviceLayer
boolean hasMetaData;
+ // Check if we need to verify/create metadata
+ // Force metadata check when:
+ // 1. checkMetaData is enabled
+ // 2. volume doesn't have disk yet (diskless -> diskful transition)
+ // 3. DISK_ADD_REQUESTED/DISK_ADDING flag is set (retry scenario where storage exists but no metadata)
+ boolean isDiskAddingState = drbdVlmData.getRscLayerObject().getAbsResource().getStateFlags()
+ .isSomeSet(workerCtx, Resource.Flags.DISK_ADD_REQUESTED, Resource.Flags.DISK_ADDING);
if (drbdVlmData.checkMetaData() ||
- // when adding a disk, DRBD believes that it is diskless but we still need to create metadata
- !drbdVlmData.hasDisk())
+ !drbdVlmData.hasDisk() ||
+ isDiskAddingState)
{
if (mdUtils.hasMetaData())
{

View file

@ -0,0 +1,141 @@
diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/DrbdAdm.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/DrbdAdm.java
index 5627d1be8..ece191292 100644
--- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/DrbdAdm.java
+++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/utils/DrbdAdm.java
@@ -42,6 +42,9 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Singleton
@@ -56,6 +58,9 @@ public class DrbdAdm
public static final int WAIT_CONNECT_RES_TIME = 10;
private static final long DOWN_WAIT_TIMEOUT_SEC = 5;
+ private static final long FORCE_DETACH_RETRY_WAIT_MS = 250;
+ private static final String BITMAP_LEAK_ERR_MSG = "already has a bitmap, this should not happen";
+ private static final Pattern BITMAP_LEAK_MINOR_PATTERN = Pattern.compile("\\bminor\\s+(\\d+)\\b");
private final ExtCmdFactory extCmdFactory;
private final AccessContext sysCtx;
@@ -131,8 +136,38 @@ public class DrbdAdm
// command.add(resName);
command.add(drbdRscData.getSuffixedResourceName());
// execute(Arrays.asList("drbdsetup", "show", drbdRscData.getSuffixedResourceName()));
- execute(command);
- // execute(Arrays.asList("drbdsetup", "show", drbdRscData.getSuffixedResourceName()));
+ String[] commandArr = command.toArray(new String[0]);
+ try
+ {
+ File nullDevice = new File(Platform.nullDevice());
+ ExtCmd extCmd = extCmdFactory.create();
+ if (Platform.isWindows())
+ {
+ extCmd.setTimeout(TimeoutType.WAIT, 5 * 60 * 1000);
+ }
+
+ OutputData outputData = extCmd.pipeExec(ProcessBuilder.Redirect.from(nullDevice), commandArr);
+ if (
+ outputData.exitCode != 0 &&
+ isBitmapLeakOnAttach(outputData) &&
+ cleanupStaleBitmapAndRetry(extCmd, nullDevice, outputData)
+ )
+ {
+ outputData = extCmd.pipeExec(ProcessBuilder.Redirect.from(nullDevice), commandArr);
+ }
+ if (outputData.exitCode != 0)
+ {
+ throw new ExtCmdFailedException(commandArr, outputData);
+ }
+ }
+ catch (ChildProcessTimeoutException timeoutExc)
+ {
+ throw new ExtCmdFailedException(commandArr, timeoutExc);
+ }
+ catch (IOException ioExc)
+ {
+ throw new ExtCmdFailedException(commandArr, ioExc);
+ }
drbdRscData.setAdjustRequired(false);
}
@@ -805,6 +840,75 @@ public class DrbdAdm
}
}
+ static boolean isBitmapLeakOnAttach(OutputData outputData)
+ {
+ return extractBitmapLeakMinor(outputData) != null;
+ }
+
+ static @Nullable Integer extractBitmapLeakMinor(OutputData outputData)
+ {
+ String stderr = new String(outputData.stderrData, StandardCharsets.UTF_8);
+ if (!stderr.contains(BITMAP_LEAK_ERR_MSG))
+ {
+ return null;
+ }
+
+ Matcher matcher = BITMAP_LEAK_MINOR_PATTERN.matcher(stderr);
+ if (!matcher.find())
+ {
+ return null;
+ }
+
+ return Integer.parseInt(matcher.group(1));
+ }
+
+ private boolean cleanupStaleBitmapAndRetry(
+ ExtCmd extCmd,
+ File nullDevice,
+ OutputData outputData
+ )
+ throws IOException, ChildProcessTimeoutException, ExtCmdFailedException
+ {
+ @Nullable Integer minor = extractBitmapLeakMinor(outputData);
+ if (minor == null)
+ {
+ return false;
+ }
+
+ OutputData detachOut = extCmd.pipeExec(
+ ProcessBuilder.Redirect.from(nullDevice),
+ DRBDSETUP_UTIL,
+ "detach",
+ Integer.toString(minor)
+ );
+ if (detachOut.exitCode == 0)
+ {
+ return true;
+ }
+
+ OutputData forceDetachOut = extCmd.pipeExec(
+ ProcessBuilder.Redirect.from(nullDevice),
+ DRBDSETUP_UTIL,
+ "detach",
+ Integer.toString(minor),
+ "--force"
+ );
+ if (forceDetachOut.exitCode != 0)
+ {
+ throw new ExtCmdFailedException(forceDetachOut.executedCommand, forceDetachOut);
+ }
+
+ try
+ {
+ Thread.sleep(FORCE_DETACH_RETRY_WAIT_MS);
+ }
+ catch (InterruptedException ignored)
+ {
+ Thread.currentThread().interrupt();
+ }
+ return true;
+ }
+
public static class DrbdPrimary implements AutoCloseable
{
private final DrbdAdm drbdAdm;

View file

@ -1,155 +0,0 @@
diff --git a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java
index 49138a8fd..2f768ca0d 100644
--- a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java
+++ b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java
@@ -83,6 +83,8 @@ import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
+import java.nio.file.Files;
+import java.nio.file.Paths;
@Singleton
public class DeviceHandlerImpl implements DeviceHandler
@@ -1646,7 +1648,10 @@ public class DeviceHandlerImpl implements DeviceHandler
private void updateDiscGran(VlmProviderObject<Resource> vlmData) throws DatabaseException, StorageException
{
String devicePath = vlmData.getDevicePath();
- if (devicePath != null && vlmData.exists())
+ // Check if device path physically exists before calling lsblk
+ // This is important for DRBD devices which might be temporarily unavailable during adjust
+ // (drbdadm adjust brings devices down/up, and kernel might not have created the device node yet)
+ if (devicePath != null && vlmData.exists() && Files.exists(Paths.get(devicePath)))
{
if (vlmData.getDiscGran() == VlmProviderObject.UNINITIALIZED_SIZE)
{
diff --git a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java
index 01967a31f..78b8195a4 100644
--- a/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java
+++ b/satellite/src/main/java/com/linbit/linstor/layer/drbd/DrbdLayer.java
@@ -592,7 +592,29 @@ public class DrbdLayer implements DeviceLayer
// The .res file might not have been generated in the prepare method since it was
// missing information from the child-layers. Now that we have processed them, we
// need to make sure the .res file exists in all circumstances.
- regenerateResFile(drbdRscData);
+ // However, if the underlying devices are not accessible (e.g., LUKS device is closed
+ // during resource deletion), we skip regenerating the res file to avoid errors
+ boolean canRegenerateResFile = true;
+ if (!skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx))
+ {
+ AbsRscLayerObject<Resource> dataChild = drbdRscData.getChildBySuffix(RscLayerSuffixes.SUFFIX_DATA);
+ if (dataChild != null)
+ {
+ for (DrbdVlmData<Resource> drbdVlmData : drbdRscData.getVlmLayerObjects().values())
+ {
+ VlmProviderObject<Resource> childVlm = dataChild.getVlmProviderObject(drbdVlmData.getVlmNr());
+ if (childVlm == null || !childVlm.exists() || childVlm.getDevicePath() == null)
+ {
+ canRegenerateResFile = false;
+ break;
+ }
+ }
+ }
+ }
+ if (canRegenerateResFile)
+ {
+ regenerateResFile(drbdRscData);
+ }
// createMetaData needs rendered resFile
for (DrbdVlmData<Resource> drbdVlmData : createMetaData)
@@ -766,19 +788,72 @@ public class DrbdLayer implements DeviceLayer
if (drbdRscData.isAdjustRequired())
{
- try
+ // Check if underlying devices are accessible before adjusting
+ // This is important for encrypted resources (LUKS) where the device
+ // might be closed during deletion
+ boolean canAdjust = true;
+
+ // IMPORTANT: Check child volumes only when disk access is actually needed.
+ // For network reconnect (StandAlone -> Connected), disk access is not required.
+ boolean needsDiskAccess = false;
+
+ // Check if there are pending operations that require disk access
+ for (DrbdVlmData<Resource> drbdVlmData : drbdRscData.getVlmLayerObjects().values())
{
- drbdUtils.adjust(
- drbdRscData,
- false,
- skipDisk,
- false
- );
+ Volume vlm = (Volume) drbdVlmData.getVolume();
+ StateFlags<Volume.Flags> vlmFlags = vlm.getFlags();
+
+ // Disk access is needed if:
+ // - creating a new volume
+ // - resizing
+ // - checking/creating metadata
+ if (!drbdVlmData.exists() ||
+ drbdVlmData.checkMetaData() ||
+ vlmFlags.isSomeSet(workerCtx, Volume.Flags.RESIZE, Volume.Flags.DRBD_RESIZE))
+ {
+ needsDiskAccess = true;
+ break;
+ }
+ }
+
+ // Check child volumes only if disk access is actually needed
+ if (needsDiskAccess && !skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx))
+ {
+ AbsRscLayerObject<Resource> dataChild = drbdRscData.getChildBySuffix(RscLayerSuffixes.SUFFIX_DATA);
+ if (dataChild != null)
+ {
+ for (DrbdVlmData<Resource> drbdVlmData : drbdRscData.getVlmLayerObjects().values())
+ {
+ VlmProviderObject<Resource> childVlm = dataChild.getVlmProviderObject(drbdVlmData.getVlmNr());
+ if (childVlm == null || !childVlm.exists() || childVlm.getDevicePath() == null)
+ {
+ canAdjust = false;
+ break;
+ }
+ }
+ }
}
- catch (ExtCmdFailedException extCmdExc)
+
+ if (canAdjust)
+ {
+ try
+ {
+ drbdUtils.adjust(
+ drbdRscData,
+ false,
+ skipDisk,
+ false
+ );
+ }
+ catch (ExtCmdFailedException extCmdExc)
+ {
+ restoreBackupResFile(drbdRscData);
+ throw extCmdExc;
+ }
+ }
+ else
{
- restoreBackupResFile(drbdRscData);
- throw extCmdExc;
+ drbdRscData.setAdjustRequired(false);
}
}
diff --git a/satellite/src/main/java/com/linbit/linstor/layer/luks/LuksLayer.java b/satellite/src/main/java/com/linbit/linstor/layer/luks/LuksLayer.java
index cdca0b6d2..89c8be9da 100644
--- a/satellite/src/main/java/com/linbit/linstor/layer/luks/LuksLayer.java
+++ b/satellite/src/main/java/com/linbit/linstor/layer/luks/LuksLayer.java
@@ -383,6 +383,7 @@ public class LuksLayer implements DeviceLayer
vlmData.setSizeState(Size.AS_EXPECTED);
vlmData.setOpened(true);
+ vlmData.setExists(true);
vlmData.setFailed(false);
}
}