[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:
commit
5674ce73e6
9 changed files with 1027 additions and 548 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -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())
|
||||
{
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue