diff --git a/conf/springConfigXml/VolumeManager.xml b/conf/springConfigXml/VolumeManager.xml
index 99c93fefa1b..f2de6f26d56 100644
--- a/conf/springConfigXml/VolumeManager.xml
+++ b/conf/springConfigXml/VolumeManager.xml
@@ -100,6 +100,7 @@
+
diff --git a/header/src/main/java/org/zstack/header/storage/primary/RollbackVolumeEncryptionOnPrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/RollbackVolumeEncryptionOnPrimaryStorageMsg.java
new file mode 100644
index 00000000000..557c8b9e6a8
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/RollbackVolumeEncryptionOnPrimaryStorageMsg.java
@@ -0,0 +1,42 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.NeedReplyMessage;
+import org.zstack.header.volume.VolumeInventory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RollbackVolumeEncryptionOnPrimaryStorageMsg extends NeedReplyMessage implements PrimaryStorageMessage {
+ private String hostUuid;
+ private VolumeInventory volume;
+ private List items = new ArrayList<>();
+
+ @Override
+ public String getPrimaryStorageUuid() {
+ return volume.getPrimaryStorageUuid();
+ }
+
+ public String getHostUuid() {
+ return hostUuid;
+ }
+
+ public void setHostUuid(String hostUuid) {
+ this.hostUuid = hostUuid;
+ }
+
+ public VolumeInventory getVolume() {
+ return volume;
+ }
+
+ public void setVolume(VolumeInventory volume) {
+ this.volume = volume;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/storage/primary/RollbackVolumeEncryptionOnPrimaryStorageReply.java b/header/src/main/java/org/zstack/header/storage/primary/RollbackVolumeEncryptionOnPrimaryStorageReply.java
new file mode 100644
index 00000000000..9c8e2684585
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/storage/primary/RollbackVolumeEncryptionOnPrimaryStorageReply.java
@@ -0,0 +1,6 @@
+package org.zstack.header.storage.primary;
+
+import org.zstack.header.message.MessageReply;
+
+public class RollbackVolumeEncryptionOnPrimaryStorageReply extends MessageReply {
+}
diff --git a/header/src/main/java/org/zstack/header/volume/VolumeBackupEncryptionConversionContext.java b/header/src/main/java/org/zstack/header/volume/VolumeBackupEncryptionConversionContext.java
new file mode 100644
index 00000000000..b848847a859
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/volume/VolumeBackupEncryptionConversionContext.java
@@ -0,0 +1,31 @@
+package org.zstack.header.volume;
+
+public class VolumeBackupEncryptionConversionContext {
+ private String volumeUuid;
+ private boolean targetEncrypted;
+ private Object extensionData;
+
+ public String getVolumeUuid() {
+ return volumeUuid;
+ }
+
+ public void setVolumeUuid(String volumeUuid) {
+ this.volumeUuid = volumeUuid;
+ }
+
+ public boolean isTargetEncrypted() {
+ return targetEncrypted;
+ }
+
+ public void setTargetEncrypted(boolean targetEncrypted) {
+ this.targetEncrypted = targetEncrypted;
+ }
+
+ public Object getExtensionData() {
+ return extensionData;
+ }
+
+ public void setExtensionData(Object extensionData) {
+ this.extensionData = extensionData;
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/volume/VolumeBackupEncryptionConversionExtensionPoint.java b/header/src/main/java/org/zstack/header/volume/VolumeBackupEncryptionConversionExtensionPoint.java
new file mode 100644
index 00000000000..c8f1b3d13db
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/volume/VolumeBackupEncryptionConversionExtensionPoint.java
@@ -0,0 +1,14 @@
+package org.zstack.header.volume;
+
+import org.zstack.header.core.ReturnValueCompletion;
+
+public interface VolumeBackupEncryptionConversionExtensionPoint {
+ void prepareVolumeBackupEncryptionConversion(VolumeInventory volume, boolean encrypted, String hostUuid,
+ ReturnValueCompletion completion);
+
+ void commitVolumeBackupEncryptionConversion(VolumeBackupEncryptionConversionContext context);
+
+ void rollbackVolumeBackupEncryptionConversion(VolumeBackupEncryptionConversionContext context);
+
+ void afterVolumeBackupEncryptionConversionCommitted(VolumeBackupEncryptionConversionContext context);
+}
diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
index e6eb650e1a4..a8dc7d2e533 100755
--- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
+++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
@@ -503,6 +503,12 @@ public static class ReplaceLuksRbdRsp extends AgentResponse {
public Long actualSize;
}
+ public static class RollbackLuksRbdCmd extends AgentCommand {
+ public String installPath;
+ public String targetInstallPath;
+ public String sourceTrashInstallPath;
+ }
+
public static class SwapInPlaceLuksRbdCmd extends AgentCommand {
public String installPath;
public String temporaryInstallPath;
@@ -1455,6 +1461,7 @@ public int compareTo(SnapInfo snapInfo) {
public static final String KVM_HOST_LUKS_RESIZE_PATH = "/ceph/primarystorage/kvmhost/luksresize";
public static final String KVM_HOST_LUKS_CONVERT_PATH = "/ceph/primarystorage/kvmhost/luksconvert";
public static final String LUKS_REPLACE_VOLUME_PATH = "/ceph/primarystorage/volume/luksreplace";
+ public static final String LUKS_ROLLBACK_CONVERT_PATH = "/ceph/primarystorage/volume/luksconvert/rollback";
public static final String LUKS_SWAP_IN_PLACE_PATH = "/ceph/primarystorage/volume/luksswapinplace";
public static final String KVM_HOST_IMAGESTORE_ENCRYPTED_DOWNLOAD_PATH = "/ceph/primarystorage/kvmhost/imagestore/encrypteddownload";
public static final String FLATTEN_PATH = "/ceph/primarystorage/volume/flatten";
@@ -3673,6 +3680,54 @@ public void fail(ErrorCode errorCode) {
});
}
+ @Override
+ protected void handle(RollbackVolumeEncryptionOnPrimaryStorageMsg msg) {
+ RollbackVolumeEncryptionOnPrimaryStorageReply reply = new RollbackVolumeEncryptionOnPrimaryStorageReply();
+ if (msg.getItems() == null || msg.getItems().size() != 1) {
+ reply.setError(operr(
+ "ceph rollback volume encryption conversion requires volume snapshots to be merged before rollback; volume[uuid:%s] has %s rollback items",
+ msg.getVolume().getUuid(), msg.getItems() == null ? 0 : msg.getItems().size()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ ConvertVolumeEncryptionOnPrimaryStorageMsg.VolumeEncryptionConversionItem item = msg.getItems().get(0);
+ if (!VolumeVO.class.getSimpleName().equals(item.getResourceType())) {
+ reply.setError(operr(
+ "ceph rollback volume encryption conversion only supports the active volume after snapshots are merged, but got resource[type:%s, uuid:%s]",
+ item.getResourceType(), item.getResourceUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ if (StringUtils.isBlank(item.getSourceInstallPath()) || item.getSourceInstallPath().contains("@")) {
+ reply.setError(operr(
+ "ceph rollback volume encryption conversion requires an active RBD source path, but got source path[%s]",
+ item.getSourceInstallPath()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ RollbackLuksRbdCmd rcmd = new RollbackLuksRbdCmd();
+ rcmd.installPath = item.getSourceInstallPath();
+ rcmd.targetInstallPath = item.getTargetInstallPath();
+ rcmd.sourceTrashInstallPath = item.getSourceTrashInstallPath();
+
+ httpCall(LUKS_ROLLBACK_CONVERT_PATH, rcmd, AgentResponse.class,
+ new ReturnValueCompletion(msg) {
+ @Override
+ public void fail(ErrorCode err) {
+ reply.setError(err);
+ bus.reply(msg, reply);
+ }
+
+ @Override
+ public void success(AgentResponse ret) {
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
@Override
protected void handle(final DownloadIsoToPrimaryStorageMsg msg) {
final DownloadIsoToPrimaryStorageReply reply = new DownloadIsoToPrimaryStorageReply();
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
index 5e4831fc1de..a79b0172a24 100755
--- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageBase.java
@@ -912,6 +912,8 @@ public void handleLocalMessage(Message msg) {
handle((EncryptVolumeBitsOnPrimaryStorageMsg) msg);
} else if (msg instanceof ConvertVolumeEncryptionOnPrimaryStorageMsg) {
handle((ConvertVolumeEncryptionOnPrimaryStorageMsg) msg);
+ } else if (msg instanceof RollbackVolumeEncryptionOnPrimaryStorageMsg) {
+ handle((RollbackVolumeEncryptionOnPrimaryStorageMsg) msg);
} else {
super.handleLocalMessage(msg);
}
@@ -959,6 +961,48 @@ public void fail(ErrorCode errorCode) {
});
}
+ protected void handle(RollbackVolumeEncryptionOnPrimaryStorageMsg msg) {
+ RollbackVolumeEncryptionOnPrimaryStorageReply reply = new RollbackVolumeEncryptionOnPrimaryStorageReply();
+ if (msg.getVolume() == null || StringUtils.isBlank(msg.getVolume().getUuid())) {
+ reply.setError(operr("rollback volume encryption on local primary storage[uuid:%s] requires volume with non-blank uuid",
+ self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ String hostUuid = StringUtils.isNotBlank(msg.getHostUuid()) ?
+ msg.getHostUuid() : null;
+ if (StringUtils.isBlank(hostUuid)) {
+ try {
+ hostUuid = getHostUuidByResourceUuid(msg.getVolume().getUuid());
+ } catch (OperationFailureException e) {
+ reply.setError(e.getErrorCode());
+ bus.reply(msg, reply);
+ return;
+ }
+ }
+ if (StringUtils.isBlank(hostUuid)) {
+ reply.setError(operr("cannot determine host for rolling back volume[uuid:%s] encryption conversion on local primary storage[uuid:%s]",
+ msg.getVolume().getUuid(), self.getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ LocalStorageHypervisorBackend bkd = getHypervisorBackendFactoryByHostUuid(hostUuid).getHypervisorBackend(self);
+ bkd.handle(msg, hostUuid, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(RollbackVolumeEncryptionOnPrimaryStorageReply returnValue) {
+ bus.reply(msg, returnValue);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
private void handle(EncryptVolumeBitsOnPrimaryStorageMsg msg) {
if (StringUtils.isBlank(msg.getHostUuid()) || StringUtils.isBlank(msg.getVolumeUuid())) {
EncryptVolumeBitsOnPrimaryStorageReply reply = new EncryptVolumeBitsOnPrimaryStorageReply();
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
index b2d5816d860..4d8729e7543 100755
--- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageHypervisorBackend.java
@@ -139,4 +139,6 @@ abstract void createEmptyVolumeWithBackingFile(VolumeInventory volume, String ho
abstract void handle(EncryptVolumeBitsOnPrimaryStorageMsg msg, ReturnValueCompletion completion);
abstract void handle(ConvertVolumeEncryptionOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
+
+ abstract void handle(RollbackVolumeEncryptionOnPrimaryStorageMsg msg, String hostUuid, ReturnValueCompletion completion);
}
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java
index e39cb78e18a..3e37a3aa402 100755
--- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java
@@ -307,6 +307,14 @@ public static class ConvertVolumeEncryptionRsp extends AgentResponse {
public Map actualSizes;
}
+ public static class RollbackVolumeEncryptionCmd extends AgentCommand implements Serializable {
+ public String volumeUuid;
+ public List items;
+ }
+
+ public static class RollbackVolumeEncryptionRsp extends AgentResponse {
+ }
+
public static class GetPhysicalCapacityCmd extends AgentCommand {
private String hostUuid;
@@ -1097,6 +1105,7 @@ public static class PrefixRebaseBackingFilesRsp extends LocalStorageKvmBackend.A
public static final String PREFIX_REBASE_BACKING_FILES_PATH = "/localstorage/snapshot/prefixrebasebackingfiles";
public static final String ENCRYPT_VOLUME_BITS_PATH = "/localstorage/volume/encryptinplace";
public static final String CONVERT_VOLUME_ENCRYPTION_PATH = "/localstorage/volume/convertencryption";
+ public static final String ROLLBACK_VOLUME_ENCRYPTION_PATH = "/localstorage/volume/convertencryption/rollback";
public LocalStorageKvmBackend() {
}
@@ -4183,6 +4192,28 @@ public void fail(ErrorCode errorCode) {
});
}
+ @Override
+ void handle(RollbackVolumeEncryptionOnPrimaryStorageMsg msg, String hostUuid,
+ ReturnValueCompletion completion) {
+ RollbackVolumeEncryptionCmd cmd = new RollbackVolumeEncryptionCmd();
+ cmd.volumeUuid = msg.getVolume().getUuid();
+ cmd.items = msg.getItems();
+
+ httpCall(ROLLBACK_VOLUME_ENCRYPTION_PATH, hostUuid, cmd, RollbackVolumeEncryptionRsp.class,
+ new ReturnValueCompletion(completion) {
+ @Override
+ public void success(RollbackVolumeEncryptionRsp rsp) {
+ completion.success(new RollbackVolumeEncryptionOnPrimaryStorageReply());
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ completion.fail(operr("failed to rollback volume[uuid:%s] encryption conversion on local storage host[uuid:%s]: %s",
+ msg.getVolume().getUuid(), hostUuid, errorCode));
+ }
+ });
+ }
+
private String prepareVolumeEncryptedDek(String hostUuid, VolumeInventory volume, boolean required) {
if (!required) {
return null;
diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java
index 04b92877dd4..8b691c047bd 100755
--- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java
+++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorage.java
@@ -140,6 +140,8 @@ protected void handleLocalMessage(Message msg) {
handle((EncryptVolumeBitsOnPrimaryStorageMsg) msg);
} else if (msg instanceof ConvertVolumeEncryptionOnPrimaryStorageMsg) {
handle((ConvertVolumeEncryptionOnPrimaryStorageMsg) msg);
+ } else if (msg instanceof RollbackVolumeEncryptionOnPrimaryStorageMsg) {
+ handle((RollbackVolumeEncryptionOnPrimaryStorageMsg) msg);
} else {
super.handleLocalMessage(msg);
}
@@ -169,6 +171,30 @@ public void fail(ErrorCode errorCode) {
});
}
+ protected void handle(RollbackVolumeEncryptionOnPrimaryStorageMsg msg) {
+ RollbackVolumeEncryptionOnPrimaryStorageReply reply = new RollbackVolumeEncryptionOnPrimaryStorageReply();
+ NfsPrimaryStorageBackend backend = getUsableBackend();
+ if (backend == null) {
+ reply.setError(operr("the NFS primary storage[uuid:%s, name:%s] cannot find any usable host to rollback volume[uuid:%s] encryption conversion",
+ self.getUuid(), self.getName(), msg.getVolume().getUuid()));
+ bus.reply(msg, reply);
+ return;
+ }
+
+ backend.handle(getSelfInventory(), msg, new ReturnValueCompletion(msg) {
+ @Override
+ public void success(RollbackVolumeEncryptionOnPrimaryStorageReply returnValue) {
+ bus.reply(msg, returnValue);
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ reply.setError(errorCode);
+ bus.reply(msg, reply);
+ }
+ });
+ }
+
private void handle(EncryptVolumeBitsOnPrimaryStorageMsg msg) {
NfsPrimaryStorageBackend backend = getUsableBackend();
if (backend == null) {
diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
index f769722cfa2..948291ad67a 100755
--- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
+++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageBackend.java
@@ -56,6 +56,8 @@ public interface NfsPrimaryStorageBackend {
void handle(PrimaryStorageInventory inv, ConvertVolumeEncryptionOnPrimaryStorageMsg msg, ReturnValueCompletion completion);
+ void handle(PrimaryStorageInventory inv, RollbackVolumeEncryptionOnPrimaryStorageMsg msg, ReturnValueCompletion completion);
+
void getPhysicalCapacity(PrimaryStorageInventory inv, ReturnValueCompletion completion);
void checkIsBitsExisting(PrimaryStorageInventory inv, String installPath, ReturnValueCompletion completion);
diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java
index abb511dbd9c..47799d4b1ee 100755
--- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java
+++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackend.java
@@ -140,6 +140,7 @@ public class NfsPrimaryStorageKVMBackend implements NfsPrimaryStorageBackend,
public static final String CREATE_VOLUME_FROM_TEMPLATE_PATH = "/nfsprimarystorage/sftp/createvolumefromtemplate";
public static final String ENCRYPT_VOLUME_BITS_PATH = "/nfsprimarystorage/volume/encryptinplace";
public static final String CONVERT_VOLUME_ENCRYPTION_PATH = "/nfsprimarystorage/volume/convertencryption";
+ public static final String ROLLBACK_VOLUME_ENCRYPTION_PATH = "/nfsprimarystorage/volume/convertencryption/rollback";
public static final String GET_QCOW2_HASH_VALUE_PATH = "/nfsprimarystorage/getqcow2hash";
public static final String WRITE_VM_METADATA_PATH = "/nfsprimarystorage/vm/metadata/write";
@@ -2387,6 +2388,40 @@ public void run(MessageReply reply) {
});
}
+ @Override
+ public void handle(PrimaryStorageInventory inv, RollbackVolumeEncryptionOnPrimaryStorageMsg msg,
+ ReturnValueCompletion completion) {
+ HostInventory host = nfsFactory.getConnectedHostForOperation(inv).get(0);
+ RollbackVolumeEncryptionCmd cmd = new RollbackVolumeEncryptionCmd();
+ cmd.setUuid(inv.getUuid());
+ cmd.volumeUuid = msg.getVolume().getUuid();
+ cmd.items = msg.getItems();
+
+ KVMHostAsyncHttpCallMsg hmsg = new KVMHostAsyncHttpCallMsg();
+ hmsg.setCommand(cmd);
+ hmsg.setPath(ROLLBACK_VOLUME_ENCRYPTION_PATH);
+ hmsg.setHostUuid(host.getUuid());
+ bus.makeTargetServiceIdByResourceUuid(hmsg, HostConstant.SERVICE_ID, host.getUuid());
+ bus.send(hmsg, new CloudBusCallBack(completion) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ completion.fail(reply.getError());
+ return;
+ }
+
+ RollbackVolumeEncryptionRsp rsp = ((KVMHostAsyncHttpCallReply) reply).toResponse(RollbackVolumeEncryptionRsp.class);
+ if (!rsp.isSuccess()) {
+ completion.fail(operr("failed to rollback volume[uuid:%s] encryption conversion on NFS primary storage[uuid:%s] via host[uuid:%s]: %s",
+ msg.getVolume().getUuid(), inv.getUuid(), host.getUuid(), rsp.getError()));
+ return;
+ }
+
+ completion.success(new RollbackVolumeEncryptionOnPrimaryStorageReply());
+ }
+ });
+ }
+
private String prepareVolumeEncryptedDek(String hostUuid, VolumeInventory volume, boolean required) {
if (!required) {
return null;
diff --git a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
index 522b4b883f2..8b124ad7fc5 100755
--- a/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
+++ b/plugin/nfsPrimaryStorage/src/main/java/org/zstack/storage/primary/nfs/NfsPrimaryStorageKVMBackendCommands.java
@@ -549,6 +549,14 @@ public static class ConvertVolumeEncryptionRsp extends NfsPrimaryStorageAgentRes
public Map actualSizes;
}
+ public static class RollbackVolumeEncryptionCmd extends NfsPrimaryStorageAgentCommand implements Serializable {
+ public String volumeUuid;
+ public List items;
+ }
+
+ public static class RollbackVolumeEncryptionRsp extends NfsPrimaryStorageAgentResponse {
+ }
+
public static class DeleteCmd extends NfsPrimaryStorageAgentCommand {
private boolean folder;
private String installPath;
diff --git a/storage/src/main/java/org/zstack/storage/encrypt/VolumeBackupEncryptionConversionExtensionHelper.java b/storage/src/main/java/org/zstack/storage/encrypt/VolumeBackupEncryptionConversionExtensionHelper.java
new file mode 100644
index 00000000000..69b1ce6fe81
--- /dev/null
+++ b/storage/src/main/java/org/zstack/storage/encrypt/VolumeBackupEncryptionConversionExtensionHelper.java
@@ -0,0 +1,129 @@
+package org.zstack.storage.encrypt;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.core.asyncbatch.While;
+import org.zstack.core.componentloader.PluginRegistry;
+import org.zstack.header.core.ReturnValueCompletion;
+import org.zstack.header.core.WhileCompletion;
+import org.zstack.header.core.WhileDoneCompletion;
+import org.zstack.header.errorcode.ErrorCode;
+import org.zstack.header.errorcode.ErrorCodeList;
+import org.zstack.header.volume.VolumeBackupEncryptionConversionContext;
+import org.zstack.header.volume.VolumeBackupEncryptionConversionExtensionPoint;
+import org.zstack.header.volume.VolumeInventory;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.zstack.core.Platform.operr;
+
+public class VolumeBackupEncryptionConversionExtensionHelper {
+ private static final CLogger logger = Utils.getLogger(VolumeBackupEncryptionConversionExtensionHelper.class);
+ @Autowired
+ private PluginRegistry pluginRgty;
+
+ public static class Context {
+ private final VolumeBackupEncryptionConversionExtensionPoint extension;
+ private final VolumeBackupEncryptionConversionContext context;
+
+ public Context(VolumeBackupEncryptionConversionExtensionPoint extension,
+ VolumeBackupEncryptionConversionContext context) {
+ this.extension = extension;
+ this.context = context;
+ }
+ }
+
+ public void prepare(VolumeInventory volume, boolean targetEncrypted, String hostUuid,
+ ReturnValueCompletion> completion) {
+ List extensions =
+ pluginRgty.getExtensionList(VolumeBackupEncryptionConversionExtensionPoint.class);
+ List contexts = Collections.synchronizedList(new ArrayList<>());
+ new While<>(extensions).each((ext, whileCompletion) ->
+ prepareExtension(ext, volume, targetEncrypted, hostUuid, contexts, whileCompletion)
+ ).run(new WhileDoneCompletion(completion) {
+ @Override
+ public void done(ErrorCodeList errorCodeList) {
+ if (errorCodeList.getCauses().isEmpty()) {
+ completion.success(new ArrayList<>(contexts));
+ return;
+ }
+
+ rollback(contexts, volume.getUuid());
+ completion.fail(errorCodeList.getCauses().get(0));
+ }
+ });
+ }
+
+ public void commit(List contexts) {
+ for (Context context : safeContexts(contexts)) {
+ context.extension.commitVolumeBackupEncryptionConversion(context.context);
+ }
+ }
+
+ public void rollback(List contexts, String volumeUuid) {
+ List reversed = new ArrayList<>(safeContexts(contexts));
+ Collections.reverse(reversed);
+ for (Context context : reversed) {
+ try {
+ context.extension.rollbackVolumeBackupEncryptionConversion(context.context);
+ } catch (RuntimeException e) {
+ logger.warn(String.format("failed to rollback volume backup encryption conversion for volume[uuid:%s]: %s",
+ volumeUuid, e.getMessage()), e);
+ }
+ }
+ }
+
+ public void cleanupCommitted(List contexts, String volumeUuid) {
+ for (Context context : safeContexts(contexts)) {
+ try {
+ context.extension.afterVolumeBackupEncryptionConversionCommitted(context.context);
+ } catch (RuntimeException e) {
+ logger.warn(String.format("failed to run volume backup encryption conversion cleanup for volume[uuid:%s]: %s",
+ volumeUuid, e.getMessage()), e);
+ }
+ }
+ }
+
+ private List safeContexts(List contexts) {
+ return contexts == null ? Collections.emptyList() : contexts;
+ }
+
+ private Context makeContext(VolumeBackupEncryptionConversionExtensionPoint extension,
+ VolumeBackupEncryptionConversionContext context) {
+ return new Context(extension, context);
+ }
+
+ private void prepareExtension(VolumeBackupEncryptionConversionExtensionPoint ext,
+ VolumeInventory volume,
+ boolean targetEncrypted,
+ String hostUuid,
+ List contexts,
+ WhileCompletion whileCompletion) {
+ try {
+ ext.prepareVolumeBackupEncryptionConversion(volume, targetEncrypted, hostUuid,
+ new ReturnValueCompletion(whileCompletion) {
+ @Override
+ public void success(VolumeBackupEncryptionConversionContext context) {
+ if (context != null) {
+ contexts.add(makeContext(ext, context));
+ }
+ whileCompletion.done();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ whileCompletion.addError(errorCode);
+ whileCompletion.allDone();
+ }
+ }
+ );
+ } catch (RuntimeException e) {
+ whileCompletion.addError(operr("failed to prepare volume backup encryption conversion for volume[uuid:%s]: %s",
+ volume.getUuid(), e.getMessage()));
+ whileCompletion.allDone();
+ }
+ }
+}
diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
index e94bf01ec58..f8c3a128dc5 100755
--- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
+++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageBase.java
@@ -188,6 +188,13 @@ protected void handle(ConvertVolumeEncryptionOnPrimaryStorageMsg msg) {
bus.reply(msg, reply);
}
+ protected void handle(RollbackVolumeEncryptionOnPrimaryStorageMsg msg) {
+ RollbackVolumeEncryptionOnPrimaryStorageReply reply = new RollbackVolumeEncryptionOnPrimaryStorageReply();
+ reply.setError(operr("primary storage[type:%s, uuid:%s] does not support rollback volume encryption conversion",
+ self.getType(), self.getUuid()));
+ bus.reply(msg, reply);
+ }
+
public PrimaryStorageBase(PrimaryStorageVO self) {
this.self = self;
}
@@ -431,6 +438,8 @@ protected void handleLocalMessage(Message msg) {
handle((GetVolumeSnapshotEncryptedOnPrimaryStorageMsg) msg);
} else if (msg instanceof ConvertVolumeEncryptionOnPrimaryStorageMsg) {
handle((ConvertVolumeEncryptionOnPrimaryStorageMsg) msg);
+ } else if (msg instanceof RollbackVolumeEncryptionOnPrimaryStorageMsg) {
+ handle((RollbackVolumeEncryptionOnPrimaryStorageMsg) msg);
} else if (msg instanceof DeleteVolumeChainOnPrimaryStorageMsg) {
handle((DeleteVolumeChainOnPrimaryStorageMsg) msg);
} else if (msg instanceof CleanUpStorageTrashOnPrimaryStorageMsg) {
diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java b/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java
index c364e790fe9..772b99df014 100755
--- a/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java
+++ b/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java
@@ -52,6 +52,7 @@
import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageMsg;
import org.zstack.storage.primary.EstimateVolumeTemplateSizeOnPrimaryStorageReply;
import org.zstack.storage.primary.PrimaryStorageGlobalConfig;
+import org.zstack.storage.encrypt.VolumeBackupEncryptionConversionExtensionHelper;
import org.zstack.storage.encrypt.VolumeEncryptedResourceKeyBackend;
import org.zstack.storage.encrypt.VolumeEncryptedSecretHelper;
import org.zstack.storage.snapshot.group.VolumeSnapshotGroupOperationValidator;
@@ -113,6 +114,8 @@ public class VolumeBase extends AbstractVolume implements Volume {
private VolumeEncryptedResourceKeyBackend volumeEncryptedResourceKeyBackend;
@Autowired
private VolumeEncryptedSecretHelper volumeEncryptedSecretHelper;
+ @Autowired
+ private VolumeBackupEncryptionConversionExtensionHelper volumeBackupEncryptionConversionExtensionHelper;
public VolumeBase(VolumeVO vo) {
self = vo;
@@ -3320,6 +3323,8 @@ private void changeVolumeEncryption(boolean targetEncrypted, ReturnValueCompleti
String sourceSecretHostUuid = sourceEncrypted ? resolveVolumeSecretHostUuid(self) : null;
String sourceSecretVmUuid = StringUtils.defaultIfBlank(self.getVmInstanceUuid(), self.getUuid());
AtomicReference conversionContext = new AtomicReference<>();
+ AtomicReference> backupConversionContexts =
+ new AtomicReference<>(Collections.emptyList());
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("change-volume-%s-encryption-to-%s", self.getUuid(), targetEncrypted));
@@ -3396,7 +3401,33 @@ public void run(MessageReply reply) {
@Override
public void rollback(FlowRollback trigger, Map data) {
- deleteConvertedVolumeEncryptionBits(conversionContext.get().items);
+ rollbackVolumeEncryptionOnPrimaryStorage(conversionContext.get().items, trigger);
+ }
+ });
+
+ flow(new Flow() {
+ String __name__ = "prepare-volume-backup-encryption-conversion";
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ volumeBackupEncryptionConversionExtensionHelper.prepare(getSelfInventory(), targetEncrypted, null,
+ new ReturnValueCompletion>(trigger) {
+ @Override
+ public void success(List contexts) {
+ backupConversionContexts.set(contexts);
+ trigger.next();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ trigger.fail(errorCode);
+ }
+ });
+ }
+
+ @Override
+ public void rollback(FlowRollback trigger, Map data) {
+ volumeBackupEncryptionConversionExtensionHelper.rollback(backupConversionContexts.get(), self.getUuid());
trigger.rollback();
}
});
@@ -3408,8 +3439,17 @@ public void rollback(FlowRollback trigger, Map data) {
public void run(FlowTrigger trigger, Map data) {
VolumeEncryptionConversionContext ctx = conversionContext.get();
updateVolumeEncryptionConversionInDb(targetEncrypted, ctx.snapshots, ctx.oldAndNewInstallPaths,
- (Map) data.get("actualSizes"));
- refreshVO();
+ (Map) data.get("actualSizes"), backupConversionContexts.get());
+ try {
+ refreshVO();
+ } catch (RuntimeException e) {
+ logger.warn(String.format("failed to refresh volume[uuid:%s] after encryption conversion DB update: %s",
+ self.getUuid(), e.getMessage()), e);
+ VolumeVO latest = dbf.findByUuid(self.getUuid(), VolumeVO.class);
+ if (latest != null) {
+ self = latest;
+ }
+ }
trigger.next();
}
@@ -3419,6 +3459,16 @@ public void rollback(FlowRollback trigger, Map data) {
}
});
+ flow(new NoRollbackFlow() {
+ String __name__ = "cleanup-converted-volume-backup-bits";
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ volumeBackupEncryptionConversionExtensionHelper.cleanupCommitted(backupConversionContexts.get(), self.getUuid());
+ trigger.next();
+ }
+ });
+
flow(new NoRollbackFlow() {
String __name__ = "cleanup-old-libvirt-secret";
@@ -3454,6 +3504,7 @@ public void run(FlowTrigger trigger, Map data) {
for (VolumeSnapshotVO snapshot : conversionContext.get().snapshots) {
volumeEncryptedResourceKeyBackend.detachKeyProviderFromSnapshot(snapshot.getUuid());
}
+ VolumeSystemTags.VOLUME_LIBVIRT_SECRET_HOST.delete(self.getUuid());
trigger.next();
}
});
@@ -3730,53 +3781,59 @@ private boolean isSharedBlockInstallPath(String installPath) {
return installPath != null && installPath.startsWith("sharedblock://");
}
- private void deleteConvertedVolumeEncryptionBits(List items) {
- Set sourceInstallPaths = items.stream()
- .map(ConvertVolumeEncryptionOnPrimaryStorageMsg.VolumeEncryptionConversionItem::getSourceInstallPath)
- .collect(Collectors.toSet());
- for (ConvertVolumeEncryptionOnPrimaryStorageMsg.VolumeEncryptionConversionItem item : items) {
- if (sourceInstallPaths.contains(item.getTargetInstallPath())) {
- continue;
+ private void rollbackVolumeEncryptionOnPrimaryStorage(List items,
+ FlowRollback trigger) {
+ RollbackVolumeEncryptionOnPrimaryStorageMsg rmsg = new RollbackVolumeEncryptionOnPrimaryStorageMsg();
+ rmsg.setVolume(VolumeInventory.valueOf(self));
+ rmsg.setItems(items);
+ bus.makeTargetServiceIdByResourceUuid(rmsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid());
+ bus.send(rmsg, new CloudBusCallBack(trigger) {
+ @Override
+ public void run(MessageReply reply) {
+ if (!reply.isSuccess()) {
+ logger.warn(String.format("failed to rollback volume[uuid:%s] encryption conversion on primary storage[uuid:%s]: %s",
+ self.getUuid(), self.getPrimaryStorageUuid(), reply.getError().getReadableDetails()));
+ }
+ trigger.rollback();
}
- DeleteVolumeBitsOnPrimaryStorageMsg dmsg = new DeleteVolumeBitsOnPrimaryStorageMsg();
- dmsg.setPrimaryStorageUuid(self.getPrimaryStorageUuid());
- dmsg.setInstallPath(item.getTargetInstallPath());
- dmsg.setBitsUuid(item.getResourceUuid());
- dmsg.setBitsType(item.getResourceType());
- dmsg.setHypervisorType(VolumeFormat.getMasterHypervisorTypeByVolumeFormat(self.getFormat()).toString());
- bus.makeTargetServiceIdByResourceUuid(dmsg, PrimaryStorageConstant.SERVICE_ID, self.getPrimaryStorageUuid());
- bus.send(dmsg);
- }
+ });
}
- @Transactional
private void updateVolumeEncryptionConversionInDb(boolean targetEncrypted, List snapshots,
Map oldAndNewInstallPaths,
- Map actualSizes) {
- VolumeSnapshotReferenceUtils.handleVolumeInstallUrlChange(self.getUuid(), oldAndNewInstallPaths);
-
- UpdateQuery q = SQL.New(VolumeVO.class)
- .eq(VolumeVO_.uuid, self.getUuid())
- .set(VolumeVO_.installPath, oldAndNewInstallPaths.get(self.getInstallPath()))
- .set(VolumeVO_.encrypted, targetEncrypted);
- if (actualSizes != null && actualSizes.get(self.getUuid()) != null) {
- q.set(VolumeVO_.actualSize, actualSizes.get(self.getUuid()));
- }
- q.update();
+ Map actualSizes,
+ List backupConversionContexts) {
+ new SQLBatch() {
+ @Override
+ protected void scripts() {
+ VolumeSnapshotReferenceUtils.handleVolumeInstallUrlChange(self.getUuid(), oldAndNewInstallPaths);
+
+ UpdateQuery q = SQL.New(VolumeVO.class)
+ .eq(VolumeVO_.uuid, self.getUuid())
+ .set(VolumeVO_.installPath, oldAndNewInstallPaths.get(self.getInstallPath()))
+ .set(VolumeVO_.encrypted, targetEncrypted);
+ if (actualSizes != null && actualSizes.get(self.getUuid()) != null) {
+ q.set(VolumeVO_.actualSize, actualSizes.get(self.getUuid()));
+ }
+ q.update();
- Map convertedSnapshotInstallPaths = new LinkedHashMap<>();
- for (VolumeSnapshotVO snapshot : snapshots) {
- String newPath = oldAndNewInstallPaths.get(snapshot.getPrimaryStorageInstallPath());
- if (StringUtils.isBlank(newPath)) {
- continue;
- }
- convertedSnapshotInstallPaths.put(snapshot.getUuid(), newPath);
- }
+ Map convertedSnapshotInstallPaths = new LinkedHashMap<>();
+ for (VolumeSnapshotVO snapshot : snapshots) {
+ String newPath = oldAndNewInstallPaths.get(snapshot.getPrimaryStorageInstallPath());
+ if (StringUtils.isBlank(newPath)) {
+ continue;
+ }
+ convertedSnapshotInstallPaths.put(snapshot.getUuid(), newPath);
+ }
- updateConvertedSnapshotPaths(convertedSnapshotInstallPaths, targetEncrypted);
- if (targetEncrypted && !convertedSnapshotInstallPaths.isEmpty()) {
- volumeEncryptedResourceKeyBackend.copyVolumeKeyRefToSnapshots(self.getUuid(), convertedSnapshotInstallPaths.keySet());
- }
+ updateConvertedSnapshotPaths(convertedSnapshotInstallPaths, targetEncrypted);
+ if (targetEncrypted && !convertedSnapshotInstallPaths.isEmpty()) {
+ volumeEncryptedResourceKeyBackend.copyVolumeKeyRefToSnapshots(self.getUuid(), convertedSnapshotInstallPaths.keySet());
+ }
+
+ volumeBackupEncryptionConversionExtensionHelper.commit(backupConversionContexts);
+ }
+ }.execute();
}
private void updateConvertedSnapshotPaths(Map snapshotInstallPaths, boolean targetEncrypted) {