host = volumeManager.findConnectedKvmHostByPrimaryStorage(
+ primaryStorageUuid, "external primary storage", operation);
+ if (!host.isSuccess()) {
+ throw new OperationFailureException(host.error);
+ }
+ return host.result;
+ }
+}
diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeInPlaceEncryptor.java b/storage/src/main/java/org/zstack/storage/volume/VolumeInPlaceEncryptor.java
index 1a809091cc5..ffc32e73128 100644
--- a/storage/src/main/java/org/zstack/storage/volume/VolumeInPlaceEncryptor.java
+++ b/storage/src/main/java/org/zstack/storage/volume/VolumeInPlaceEncryptor.java
@@ -13,8 +13,11 @@
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.keyprovider.EncryptedResourceKeyManager;
import org.zstack.header.message.MessageReply;
+import org.zstack.header.storage.primary.DeleteVolumeBitsOnPrimaryStorageMsg;
import org.zstack.header.storage.primary.EncryptVolumeBitsOnPrimaryStorageMsg;
+import org.zstack.header.storage.primary.EncryptVolumeBitsOnPrimaryStorageReply;
import org.zstack.header.storage.primary.PrimaryStorageConstant;
+import org.zstack.header.volume.VolumeFormat;
import org.zstack.header.volume.VolumeVO;
import org.zstack.storage.encrypt.VolumeEncryptedResourceKeyBackend;
import org.zstack.storage.encrypt.VolumeEncryptedSecretHelper;
@@ -24,7 +27,7 @@
import static org.zstack.core.Platform.operr;
/**
- * Performs an in-place LUKS conversion of an existing volume's bits.
+ * Performs a LUKS conversion of an existing volume's bits.
*
* This is the single source of truth for the "encrypt-in-place" workflow that was
* previously duplicated between {@link VolumeBase#handleMessage} (for the
@@ -38,9 +41,9 @@
*
Materialize a DEK via {@link EncryptedResourceKeyManager#getOrCreateKey}.
* Seal the DEK for the target host and pass it as {@code encryptedDek}
* to the primary storage backend.
- * Ask the primary storage backend to LUKS-convert the bits in place via
- * {@code EncryptVolumeBitsOnPrimaryStorageMsg}. The kvmagent creates any
- * single-use secret material file locally.
+ * Ask the primary storage backend to LUKS-convert the bits via
+ * {@code EncryptVolumeBitsOnPrimaryStorageMsg}. The backend may return a
+ * replacement install path.
* Persist {@code VolumeVO.encrypted = true} after a successful conversion.
*
*
@@ -246,7 +249,7 @@ public void fail(ErrorCode err) {
return;
}
- // 4) Ask the PS backend to LUKS-convert the bits in place.
+ // 4) Ask the PS backend to LUKS-convert the bits.
EncryptVolumeBitsOnPrimaryStorageMsg emsg = new EncryptVolumeBitsOnPrimaryStorageMsg();
emsg.setPrimaryStorageUuid(psUuid);
emsg.setHostUuid(ctx.getHostUuid());
@@ -262,17 +265,38 @@ public void run(MessageReply r) {
r.getError(), completion);
return;
}
+ EncryptVolumeBitsOnPrimaryStorageReply reply = r.castReply();
+ String newInstallPath = reply.getInstallPath();
+ boolean installPathChanged = StringUtils.isNotBlank(newInstallPath)
+ && !StringUtils.equals(newInstallPath, installPath);
+ if (installPathChanged) {
+ volume.setInstallPath(newInstallPath);
+ }
// 5) Persist encrypted=true. The short-circuit above guarantees we only
// reach here when the row was previously encrypted=false; this is the
// one and only place the flag flips, ensuring it always reflects the
// on-disk reality.
volume.setEncrypted(true);
VolumeVO latest = dbf.updateAndRefresh(volume);
+ if (installPathChanged) {
+ deleteOldVolumeBitsBestEffort(psUuid, volume, installPath);
+ }
completion.success(latest);
}
});
}
+ private void deleteOldVolumeBitsBestEffort(String psUuid, VolumeVO volume, String oldInstallPath) {
+ DeleteVolumeBitsOnPrimaryStorageMsg dmsg = new DeleteVolumeBitsOnPrimaryStorageMsg();
+ dmsg.setPrimaryStorageUuid(psUuid);
+ dmsg.setInstallPath(oldInstallPath);
+ dmsg.setBitsUuid(volume.getUuid());
+ dmsg.setBitsType(VolumeVO.class.getSimpleName());
+ dmsg.setHypervisorType(VolumeFormat.getMasterHypervisorTypeByVolumeFormat(volume.getFormat()).toString());
+ bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, psUuid);
+ bus.send(dmsg);
+ }
+
private void failAfterRollingBackMetadata(String volumeUuid,
boolean attachedProviderHere,
EncryptedResourceKeyManager.ResourceKeyResult keyResult,
diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java
index bf032d5df69..246279580db 100755
--- a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java
+++ b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java
@@ -514,6 +514,9 @@ public void run(FlowTrigger trigger, Map data) {
volumeInPlaceEncryptor.encryptInPlace(vol, ctx, new ReturnValueCompletion(trigger) {
@Override
public void success(VolumeVO latest) {
+ if (StringUtils.isNotBlank(latest.getInstallPath())) {
+ primaryStorageInstallPath = latest.getInstallPath();
+ }
trigger.next();
}
diff --git a/test/src/test/groovy/org/zstack/storage/volume/VolumeManagerImplHostSelectionTest.groovy b/test/src/test/groovy/org/zstack/storage/volume/VolumeManagerImplHostSelectionTest.groovy
index a6e1505cc81..b6b30b4bc6f 100644
--- a/test/src/test/groovy/org/zstack/storage/volume/VolumeManagerImplHostSelectionTest.groovy
+++ b/test/src/test/groovy/org/zstack/storage/volume/VolumeManagerImplHostSelectionTest.groovy
@@ -76,6 +76,21 @@ class VolumeManagerImplHostSelectionTest {
Mockito.verify(hostQuery).add(HostVO_.uuid, SimpleQuery.Op.IN, Arrays.asList("host-connected"))
}
+ @Test
+ void testDisconnectedPsHostRefsDoNotFallback() {
+ Mockito.when(clusterRefQuery.listValue()).thenReturn(Arrays.asList("cluster-uuid"))
+ Mockito.when(hostRefQuery.list()).thenReturn(Arrays.asList(
+ primaryStorageHostRef("host-disconnected-1", PrimaryStorageHostStatus.Disconnected),
+ primaryStorageHostRef("host-disconnected-2", PrimaryStorageHostStatus.Disconnected)
+ ))
+
+ HostInventory inventory = invokeSelectHostForEncryptInPlace()
+
+ assert inventory == null : "encrypt-in-place should not fallback to arbitrary cluster host when PS has only disconnected host refs"
+ Mockito.verify(hostQuery, Mockito.never()).add(HostVO_.uuid, SimpleQuery.Op.IN, Mockito.anyList())
+ Mockito.verify(hostQuery, Mockito.never()).find()
+ }
+
private HostInventory invokeSelectHostForEncryptInPlace() {
Method method = VolumeManagerImpl.class.getDeclaredMethod("selectHostForEncryptInPlace", String.class)
method.accessible = true
diff --git a/test/src/test/groovy/org/zstack/test/unittest/storage/encrypt/ZbsVolumeEncryptionExtensionTest.groovy b/test/src/test/groovy/org/zstack/test/unittest/storage/encrypt/ZbsVolumeEncryptionExtensionTest.groovy
new file mode 100644
index 00000000000..7c997fc8d14
--- /dev/null
+++ b/test/src/test/groovy/org/zstack/test/unittest/storage/encrypt/ZbsVolumeEncryptionExtensionTest.groovy
@@ -0,0 +1,61 @@
+package org.zstack.test.unittest.storage.encrypt
+
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+import org.zstack.header.storage.snapshot.VolumeSnapshotInventory
+import org.zstack.header.volume.VolumeVO
+import org.zstack.storage.encrypt.VolumeSnapshotEncryptionHelper
+import org.zstack.storage.encrypt.ZbsVolumeEncryptionExtension
+
+import java.lang.reflect.Field
+
+class ZbsVolumeEncryptionExtensionTest {
+ private ZbsVolumeEncryptionExtension extension
+ private VolumeSnapshotEncryptionHelper snapshotEncryptionHelper
+
+ @Before
+ void setUp() {
+ extension = new ZbsVolumeEncryptionExtension()
+ snapshotEncryptionHelper = Mockito.mock(VolumeSnapshotEncryptionHelper.class)
+ setField("snapshotEncryptionHelper", snapshotEncryptionHelper)
+ }
+
+ @Test
+ void testSnapshotHooksDelegateForEncryptedVolume() {
+ VolumeVO volume = new VolumeVO()
+ volume.uuid = "volume-uuid"
+ volume.encrypted = true
+
+ VolumeSnapshotInventory snapshot = new VolumeSnapshotInventory()
+ snapshot.uuid = "snapshot-uuid"
+
+ extension.beforeTakeSnapshot("ps-uuid", volume, snapshot)
+ extension.afterTakeSnapshot("ps-uuid", volume, snapshot)
+
+ Mockito.verify(snapshotEncryptionHelper).inheritVolumeKeyToSnapshot(volume, snapshot)
+ Mockito.verify(snapshotEncryptionHelper).completeTakeSnapshot(volume, snapshot)
+ }
+
+ @Test
+ void testSnapshotHooksSkipPlainVolume() {
+ VolumeVO volume = new VolumeVO()
+ volume.uuid = "volume-uuid"
+ volume.encrypted = false
+
+ VolumeSnapshotInventory snapshot = new VolumeSnapshotInventory()
+ snapshot.uuid = "snapshot-uuid"
+
+ extension.beforeTakeSnapshot("ps-uuid", volume, snapshot)
+ extension.afterTakeSnapshot("ps-uuid", volume, snapshot)
+
+ Mockito.verify(snapshotEncryptionHelper, Mockito.never()).inheritVolumeKeyToSnapshot(volume, snapshot)
+ Mockito.verify(snapshotEncryptionHelper, Mockito.never()).completeTakeSnapshot(volume, snapshot)
+ }
+
+ private void setField(String name, Object value) {
+ Field field = ZbsVolumeEncryptionExtension.class.getDeclaredField(name)
+ field.accessible = true
+ field.set(extension, value)
+ }
+}