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) {