diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostAndPrimaryStorageFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostAndPrimaryStorageFlow.java index 46eef6487d7..2b2f0b5fe06 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostAndPrimaryStorageFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostAndPrimaryStorageFlow.java @@ -159,6 +159,10 @@ public void run(final FlowTrigger trigger, final Map data) { // local + non-local, need to automatically allocate the primary storage List> psCombos = getPrimaryStorageCombinationFromSpec(spec, localPsUuids, nonLocalPsUuids); + if (hasUnavailablePrimaryStorageCombination(psCombos, needCreateDataVolume(spec))) { + trigger.fail(Platform.operr("no primary storage candidate is available")); + return; + } new While<>(psCombos).each((combo, whileCompletion) -> { spec.setRequiredPrimaryStorageUuidForRootVolume(combo.get(0)); spec.setRequiredPrimaryStorageUuidForDataVolume(combo.get(1)); @@ -421,6 +425,23 @@ public void handle(ErrorCode errCode, Map data) { chain.start(); } + private boolean hasUnavailablePrimaryStorageCombination(List> psCombos, boolean needCreateDataVolume) { + if (psCombos.isEmpty()) { + return true; + } + + for (List combo : psCombos) { + if (combo.get(0) == null || combo.get(0).isEmpty()) { + return true; + } + if (needCreateDataVolume && (combo.get(1) == null || combo.get(1).isEmpty())) { + return true; + } + } + + return false; + } + private List> getPrimaryStorageCombinationFromSpec(VmInstanceSpec spec, List localPsUuids, List nonLocalPsUuids) { List availPsForRootVolume = new ArrayList(); availPsForRootVolume.addAll(localPsUuids); @@ -455,6 +476,7 @@ private List> getPrimaryStorageCombinationFromSpec(VmInstanceSpec s } else { dataPs.add(null); } + filterPrimaryStorageCandidates(spec, rootPs, dataPs); List> finalPsCombos = new ArrayList<>(); if (autoAllocateRootVolumePs && autoAllocateDataVolumePs) { @@ -478,6 +500,13 @@ private List> getPrimaryStorageCombinationFromSpec(VmInstanceSpec s return finalPsCombos; } + private void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPs, List dataPs) { + for (VmAllocatePrimaryStorageExtensionPoint ext : + pluginRgty.getExtensionList(VmAllocatePrimaryStorageExtensionPoint.class)) { + ext.filterPrimaryStorageCandidates(spec, rootPs, dataPs); + } + } + private void sortPrimaryStorages(final List psUuids, String strategy, VmInstanceSpec.ImageSpec imageSpec) { List primaryStorageVOS = psUuids.stream().map(uuid -> dbf.findByUuid(uuid, PrimaryStorageVO.class)).collect(Collectors.toList()); for (PrimaryStorageSortExtensionPoint ext : pluginRgty.getExtensionList(PrimaryStorageSortExtensionPoint.class)) { diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java index a2ca811afcc..3ffc3e0905c 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java @@ -146,6 +146,7 @@ private AllocatePrimaryStorageSpaceMsg buildMessageForRootVolume(final VmInstanc } else { rmsg.setCandidatePrimaryStorageUuids(candidatePs); rmsg.setPossiblePrimaryStorageTypes(selectPsTypesFromSpec(spec)); + rmsg.setEncryptedVolumeAutoAllocation(disk != null && Boolean.TRUE.equals(disk.getEncrypted())); } Set tags = new HashSet<>(); @@ -180,6 +181,9 @@ private List buildMessageForDataVolumes(final Vm amsg.setAllocationStrategy(PrimaryStorageConstant.DEFAULT_PRIMARY_STORAGE_ALLOCATION_STRATEGY_TYPE); amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); amsg.setDiskOfferingUuid(deprecatedDisk != null ? deprecatedDisk.getDiskOfferingUuid() : null); + amsg.setEncryptedVolumeAutoAllocation(deprecatedDisk != null + && Boolean.TRUE.equals(deprecatedDisk.getEncrypted()) + && deprecatedDisk.getPrimaryStorageUuid() == null); Set tags = new HashSet<>(); if (spec.getDataVolumeSystemTags() != null) { diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageForAttachingDiskFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageForAttachingDiskFlow.java index 633394470cc..129750e8c42 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageForAttachingDiskFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageForAttachingDiskFlow.java @@ -54,6 +54,8 @@ public void run(final FlowTrigger chain, final Map data) { amsg.setSize(volume.getSize()); amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); amsg.setDiskOfferingUuid(volume.getDiskOfferingUuid()); + amsg.setEncryptedVolumeAutoAllocation(Boolean.TRUE.equals(volume.getEncrypted()) + && volume.getPrimaryStorageUuid() == null); amsg.setServiceId(bus.makeLocalServiceId(PrimaryStorageConstant.SERVICE_ID)); if (volume.isShareable()) { diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java index fa41bf2b470..ae3a4ad20b2 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java @@ -1190,6 +1190,7 @@ private void validate(APICreateVmInstanceMsg msg) { validatePsWhetherSameCluster(msg); validateDataDiskAOs(msg); + validateEncryptedVolumePrimaryStorage(msg); if (msg.getAllocatorStrategy() != null && !HostAllocatorStrategyType.hasType(msg.getAllocatorStrategy())) { throw new ApiMessageInterceptionException( @@ -1197,6 +1198,52 @@ private void validate(APICreateVmInstanceMsg msg) { } } + private void validateEncryptedVolumePrimaryStorage(APICreateVmInstanceMsg msg) { + if (CollectionUtils.isEmpty(msg.getDiskAOs())) { + return; + } + + DiskAO rootDiskAO = findOneOrNull(msg.getDiskAOs(), DiskAO::isBoot); + if (rootDiskAO != null) { + validateEncryptedVolumePrimaryStorage(rootDiskAO.getEncrypted(), msg.getPrimaryStorageUuidForRootVolume()); + } + + for (DiskAO diskAO : msg.getDiskAOs()) { + validateEncryptedVolumePrimaryStorage(diskAO.getEncrypted(), diskAO.getPrimaryStorageUuid()); + } + + if (msg.getSystemTags() == null || msg.getSystemTags().isEmpty()) { + return; + } + + String primaryStorageUuidForDataVolume = SystemTagUtils.findTagValue(msg.getSystemTags(), + VmSystemTags.PRIMARY_STORAGE_UUID_FOR_DATA_VOLUME, + VmSystemTags.PRIMARY_STORAGE_UUID_FOR_DATA_VOLUME_TOKEN); + if (primaryStorageUuidForDataVolume == null) { + return; + } + + for (DiskAO diskAO : msg.getDiskAOs()) { + if (!diskAO.isBoot()) { + validateEncryptedVolumePrimaryStorage(diskAO.getEncrypted(), primaryStorageUuidForDataVolume); + } + } + } + + private void validateEncryptedVolumePrimaryStorage(Boolean encrypted, String primaryStorageUuid) { + if (!Boolean.TRUE.equals(encrypted) || primaryStorageUuid == null) { + return; + } + + for (VmAllocatePrimaryStorageExtensionPoint ext : + pluginRgty.getExtensionList(VmAllocatePrimaryStorageExtensionPoint.class)) { + if (!ext.isPrimaryStorageSupportedForEncryptedVolume(primaryStorageUuid)) { + throw new ApiMessageInterceptionException(operr( + "primary storage[uuid:%s] does not support encrypted volume", primaryStorageUuid)); + } + } + } + private void validateDataDiskAOs(APICreateVmInstanceMsg msg) { if (CollectionUtils.isEmpty(msg.getDiskAOs())) { return; diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java index d6dd34d38b6..402de3ad40f 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java @@ -131,6 +131,8 @@ public void run(final FlowTrigger innerTrigger, Map data) { amsg.setRequiredPrimaryStorageUuid(diskAO.getPrimaryStorageUuid()); amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); amsg.setTags(diskAO.getSystemTags()); + amsg.setEncryptedVolumeAutoAllocation(Boolean.TRUE.equals(diskAO.getEncrypted()) + && diskAO.getPrimaryStorageUuid() == null); bus.makeLocalServiceId(amsg, PrimaryStorageConstant.SERVICE_ID); bus.send(amsg, new CloudBusCallBack(innerTrigger) { @Override @@ -281,6 +283,8 @@ public void run(final FlowTrigger innerTrigger, Map data) { List bsUuids = template.getBackupStorageRefs().stream() .map(ImageBackupStorageRefVO::getBackupStorageUuid).collect(Collectors.toList()); amsg.setPossiblePrimaryStorageTypes(possiblePrimaryStorageTypes(bsUuids)); + amsg.setEncryptedVolumeAutoAllocation(Boolean.TRUE.equals(diskAO.getEncrypted()) + && diskAO.getPrimaryStorageUuid() == null); bus.makeLocalServiceId(amsg, PrimaryStorageConstant.SERVICE_ID); bus.send(amsg, new CloudBusCallBack(innerTrigger) { diff --git a/conf/springConfigXml/VolumeManager.xml b/conf/springConfigXml/VolumeManager.xml index 99c93fefa1b..678b305f43e 100644 --- a/conf/springConfigXml/VolumeManager.xml +++ b/conf/springConfigXml/VolumeManager.xml @@ -120,6 +120,13 @@ + + + + + + diff --git a/header/src/main/java/org/zstack/header/storage/primary/AllocatePrimaryStorageMsg.java b/header/src/main/java/org/zstack/header/storage/primary/AllocatePrimaryStorageMsg.java index e4c3b3e5590..d88f11d7035 100755 --- a/header/src/main/java/org/zstack/header/storage/primary/AllocatePrimaryStorageMsg.java +++ b/header/src/main/java/org/zstack/header/storage/primary/AllocatePrimaryStorageMsg.java @@ -46,6 +46,7 @@ public class AllocatePrimaryStorageMsg extends NeedReplyMessage { private String volumeUuid; private boolean noOverProvisioning; private String purpose; + private boolean encryptedVolumeAutoAllocation; public Set getRequiredFeatures() { return requiredFeatures; @@ -251,4 +252,12 @@ public String getVolumeUuid() { public void setVolumeUuid(String volumeUuid) { this.volumeUuid = volumeUuid; } + + public boolean isEncryptedVolumeAutoAllocation() { + return encryptedVolumeAutoAllocation; + } + + public void setEncryptedVolumeAutoAllocation(boolean encryptedVolumeAutoAllocation) { + this.encryptedVolumeAutoAllocation = encryptedVolumeAutoAllocation; + } } diff --git a/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageAllocationSpec.java b/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageAllocationSpec.java index 13bc292157b..458cef0eb3e 100755 --- a/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageAllocationSpec.java +++ b/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageAllocationSpec.java @@ -25,6 +25,7 @@ public class PrimaryStorageAllocationSpec { private List excludePrimaryStorageTypes; private String backupStorageUuid; private Set requiredFeatures; + private boolean encryptedVolumeAutoAllocation; public Set getRequiredFeatures() { return requiredFeatures; @@ -169,4 +170,12 @@ public long getTotalSize() { public void setTotalSize(Long totalSize) { this.totalSize = totalSize; } + + public boolean isEncryptedVolumeAutoAllocation() { + return encryptedVolumeAutoAllocation; + } + + public void setEncryptedVolumeAutoAllocation(boolean encryptedVolumeAutoAllocation) { + this.encryptedVolumeAutoAllocation = encryptedVolumeAutoAllocation; + } } diff --git a/header/src/main/java/org/zstack/header/vm/VmAllocatePrimaryStorageExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmAllocatePrimaryStorageExtensionPoint.java new file mode 100644 index 00000000000..93bdbb70474 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmAllocatePrimaryStorageExtensionPoint.java @@ -0,0 +1,19 @@ +package org.zstack.header.vm; + +import org.zstack.header.storage.primary.PrimaryStorageAllocationSpec; +import org.zstack.header.storage.primary.PrimaryStorageVO; + +import java.util.List; + +public interface VmAllocatePrimaryStorageExtensionPoint { + default void filterPrimaryStorageCandidates(PrimaryStorageAllocationSpec spec, List candidates) { + } + + default void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPrimaryStorageUuids, + List dataPrimaryStorageUuids) { + } + + default boolean isPrimaryStorageSupportedForEncryptedVolume(String primaryStorageUuid) { + return true; + } +} diff --git a/storage/src/main/java/org/zstack/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtension.java b/storage/src/main/java/org/zstack/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtension.java new file mode 100644 index 00000000000..85aa2a9fb64 --- /dev/null +++ b/storage/src/main/java/org/zstack/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtension.java @@ -0,0 +1,109 @@ +package org.zstack.storage.encrypt; + +import org.zstack.core.db.Q; +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO; +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO_; +import org.zstack.header.storage.primary.PrimaryStorageAllocationSpec; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.vm.DiskAO; +import org.zstack.header.vm.VmAllocatePrimaryStorageExtensionPoint; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.volume.VolumeProtocol; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class EncryptedVolumePrimaryStorageAllocatorExtension implements VmAllocatePrimaryStorageExtensionPoint { + private static final String ZHPS_PRIMARY_STORAGE_IDENTITY = "expon"; + private static final String ZHPS_PRIMARY_STORAGE_PROTOCOL = VolumeProtocol.Vhost.name(); + + @Override + public void filterPrimaryStorageCandidates(PrimaryStorageAllocationSpec spec, List candidates) { + if (!requiresEncryptedVolumeAutoPsFilter(spec, candidates)) { + return; + } + + filterEncryptedVolumeUnsupportedPrimaryStorage(candidates); + } + + @Override + public void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPrimaryStorageUuids, + List dataPrimaryStorageUuids) { + if (requiresEncryptedRootVolumeAutoPsFilter(spec, rootPrimaryStorageUuids)) { + filterEncryptedVolumeUnsupportedPrimaryStorageUuids(rootPrimaryStorageUuids); + } + + if (requiresEncryptedDataVolumeAutoPsFilter(spec, dataPrimaryStorageUuids)) { + filterEncryptedVolumeUnsupportedPrimaryStorageUuids(dataPrimaryStorageUuids); + } + } + + @Override + public boolean isPrimaryStorageSupportedForEncryptedVolume(String primaryStorageUuid) { + return !isEncryptedVolumeUnsupportedPrimaryStorage(primaryStorageUuid); + } + + private void filterEncryptedVolumeUnsupportedPrimaryStorage(List candidates) { + List psUuids = candidates.stream().map(PrimaryStorageVO::getUuid).collect(Collectors.toList()); + Set unsupportedPsUuids = new HashSet<>(getEncryptedVolumeUnsupportedPrimaryStorageUuids(psUuids)); + candidates.removeIf(ps -> unsupportedPsUuids.contains(ps.getUuid())); + } + + private boolean requiresEncryptedVolumeAutoPsFilter(PrimaryStorageAllocationSpec spec, List candidates) { + return spec != null && spec.isEncryptedVolumeAutoAllocation() + && candidates != null && !candidates.isEmpty(); + } + + private boolean requiresEncryptedRootVolumeAutoPsFilter(VmInstanceSpec spec, List candidates) { + return spec != null && isEncryptedAutoAllocatedRootVolume(spec) + && candidates != null && !candidates.isEmpty(); + } + + private boolean requiresEncryptedDataVolumeAutoPsFilter(VmInstanceSpec spec, List candidates) { + return spec != null && hasEncryptedAutoAllocatedDataVolume(spec) + && candidates != null && !candidates.isEmpty(); + } + + private void filterEncryptedVolumeUnsupportedPrimaryStorageUuids(List candidates) { + Set unsupportedPsUuids = new HashSet<>(getEncryptedVolumeUnsupportedPrimaryStorageUuids(candidates)); + filterPrimaryStorageUuids(candidates, unsupportedPsUuids); + } + + private void filterPrimaryStorageUuids(List candidates, Set unsupportedPsUuids) { + candidates.removeIf(unsupportedPsUuids::contains); + } + + private boolean isEncryptedVolumeUnsupportedPrimaryStorage(String primaryStorageUuid) { + return primaryStorageUuid != null + && !getEncryptedVolumeUnsupportedPrimaryStorageUuids(Collections.singletonList(primaryStorageUuid)).isEmpty(); + } + + private boolean hasEncryptedAutoAllocatedDataVolume(VmInstanceSpec spec) { + return spec.getNonTemplateDeprecatedDisksSpecs() != null + && spec.getNonTemplateDeprecatedDisksSpecs().stream().anyMatch(disk -> isEncrypted(disk) + && disk.getPrimaryStorageUuid() == null); + } + + private boolean isEncryptedAutoAllocatedRootVolume(VmInstanceSpec spec) { + return isEncrypted(spec.getRootDisk()) + && spec.getCandidatePrimaryStorageUuidsForRootVolume().isEmpty(); + } + + private boolean isEncrypted(DiskAO disk) { + return disk != null && Boolean.TRUE.equals(disk.getEncrypted()); + } + + protected List getEncryptedVolumeUnsupportedPrimaryStorageUuids(List psUuids) { + Q query = Q.New(ExternalPrimaryStorageVO.class) + .select(ExternalPrimaryStorageVO_.uuid) + .eq(ExternalPrimaryStorageVO_.identity, ZHPS_PRIMARY_STORAGE_IDENTITY) + .eq(ExternalPrimaryStorageVO_.defaultProtocol, ZHPS_PRIMARY_STORAGE_PROTOCOL); + if (psUuids != null) { + query.in(ExternalPrimaryStorageVO_.uuid, psUuids); + } + return query.listValues(); + } +} diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageMainAllocatorFlow.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageMainAllocatorFlow.java index ba754882b49..110905da312 100755 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageMainAllocatorFlow.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageMainAllocatorFlow.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.transaction.annotation.Transactional; +import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; import org.zstack.core.db.SQL; @@ -15,6 +16,7 @@ import org.zstack.header.storage.backup.BackupStorageVO; import org.zstack.header.storage.primary.*; import org.zstack.header.storage.primary.PrimaryStorageConstant.AllocatorParams; +import org.zstack.header.vm.VmAllocatePrimaryStorageExtensionPoint; import org.zstack.header.vo.ResourceVO; import org.zstack.utils.Utils; import org.zstack.utils.gson.JSONObjectUtil; @@ -41,6 +43,8 @@ public class PrimaryStorageMainAllocatorFlow extends NoRollbackFlow { protected ErrorFacade errf; @Autowired protected PrimaryStorageOverProvisioningManager ratioMgr; + @Autowired + protected PluginRegistry pluginRgty; private class Result { List result; @@ -151,6 +155,7 @@ private Result allocate(Map data) { } } + filterPrimaryStorageCandidates(spec, vos); List res = new ArrayList<>(); if (PrimaryStorageAllocationPurpose.CreateNewVm.toString().equals(spec.getPurpose())) { @@ -171,6 +176,13 @@ private Result allocate(Map data) { return ret; } + private void filterPrimaryStorageCandidates(PrimaryStorageAllocationSpec spec, List candidates) { + for (VmAllocatePrimaryStorageExtensionPoint ext : + pluginRgty.getExtensionList(VmAllocatePrimaryStorageExtensionPoint.class)) { + ext.filterPrimaryStorageCandidates(spec, candidates); + } + } + @Transactional(readOnly = true) private Collection considerImageCache(PrimaryStorageAllocationSpec spec, List vos) { if (spec.getImageUuid() == null) { diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java index eac33b9334a..15f52415e53 100755 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java @@ -888,6 +888,7 @@ private PrimaryStorageAllocationSpec buildAllocateSpecFromMsg(AllocatePrimarySto spec.setAllocationMessage(msg); spec.setAvoidPrimaryStorageUuids(msg.getExcludePrimaryStorageUuids()); spec.setCandidatePrimaryStorageUuids(msg.getCandidatePrimaryStorageUuids()); + spec.setEncryptedVolumeAutoAllocation(msg.isEncryptedVolumeAutoAllocation()); if (CollectionUtils.isEmpty(msg.getCandidatePrimaryStorageUuids()) && msg.getDiskOfferingUuid() != null && DiskOfferingSystemTags.DISK_OFFERING_USER_CONFIG.hasTag(msg.getDiskOfferingUuid())) { DiskOfferingUserConfig config = OfferingUserConfigUtils.getDiskOfferingConfig(msg.getDiskOfferingUuid(), DiskOfferingUserConfig.class); diff --git a/storage/src/main/java/org/zstack/storage/volume/InstantiateVolumeForNewCreatedVmExtension.java b/storage/src/main/java/org/zstack/storage/volume/InstantiateVolumeForNewCreatedVmExtension.java index 74fef3bfc0b..54d0393eb2f 100755 --- a/storage/src/main/java/org/zstack/storage/volume/InstantiateVolumeForNewCreatedVmExtension.java +++ b/storage/src/main/java/org/zstack/storage/volume/InstantiateVolumeForNewCreatedVmExtension.java @@ -245,7 +245,10 @@ public void preInstantiateVmResource(VmInstanceSpec spec, Completion completion) } for (DiskAO diskAO : templateDataDisks) { - String psUuid = diskAO.getPrimaryStorageUuid() != null ? diskAO.getPrimaryStorageUuid() : defaultPsUuid; + String psUuid = diskAO.getPrimaryStorageUuid(); + if (psUuid == null && !Boolean.TRUE.equals(diskAO.getEncrypted())) { + psUuid = defaultPsUuid; + } msgs.add(buildCreateDataVolumeFromTemplateMsg(spec, diskAO.getTemplateUuid(), psUuid, diskAO.getSystemTags(), accountUuid, diskAO.getEncrypted())); } diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java b/storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java index 3c6b85a2c2b..0c3d4c43cfe 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeApiInterceptor.java @@ -47,6 +47,7 @@ import org.zstack.header.tag.SystemTagVO; import org.zstack.header.vm.APICreateVmInstanceMsg; import org.zstack.header.vm.DiskAO; +import org.zstack.header.vm.VmAllocatePrimaryStorageExtensionPoint; import org.zstack.header.vm.VmInstanceInventory; import org.zstack.header.vm.VmInstanceState; import org.zstack.header.vm.VmInstanceVO; @@ -161,6 +162,8 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti } private void validate(VolumeCreateMessage msg) { + validateEncryptedVolumePrimaryStorage(msg.getEncrypted(), msg.getPrimaryStorageUuid()); + String diskOffering = msg.getDiskOfferingUuid(); if (diskOffering == null || !DiskOfferingSystemTags.DISK_OFFERING_USER_CONFIG.hasTag(diskOffering)) { return; @@ -610,12 +613,13 @@ private void validate(APIFlattenVolumeMsg msg) { private void validate(APIChangeVolumeEncryptionMsg msg) { Tuple t = Q.New(VolumeVO.class) - .select(VolumeVO_.status, VolumeVO_.state, VolumeVO_.vmInstanceUuid) + .select(VolumeVO_.status, VolumeVO_.state, VolumeVO_.vmInstanceUuid, VolumeVO_.primaryStorageUuid) .eq(VolumeVO_.uuid, msg.getVolumeUuid()) .findTuple(); VolumeStatus status = (VolumeStatus) t.get(0); VolumeState state = (VolumeState) t.get(1); String vmUuid = (String) t.get(2); + String primaryStorageUuid = (String) t.get(3); if (status != VolumeStatus.Ready) { throw new ApiMessageInterceptionException(operr( @@ -631,6 +635,7 @@ private void validate(APIChangeVolumeEncryptionMsg msg) { throw new ApiMessageInterceptionException(operr( "volume[uuid:%s] is attached to a host, cannot change encryption", msg.getVolumeUuid())); } + validateEncryptedVolumePrimaryStorage(msg.isEncrypted(), primaryStorageUuid); if (vmUuid == null) { return; } @@ -727,6 +732,20 @@ protected void validate(APICreateVmInstanceMsg msg) { } } + private void validateEncryptedVolumePrimaryStorage(Boolean encrypted, String primaryStorageUuid) { + if (!Boolean.TRUE.equals(encrypted) || primaryStorageUuid == null) { + return; + } + + for (VmAllocatePrimaryStorageExtensionPoint ext : + pluginRgty.getExtensionList(VmAllocatePrimaryStorageExtensionPoint.class)) { + if (!ext.isPrimaryStorageSupportedForEncryptedVolume(primaryStorageUuid)) { + throw new ApiMessageInterceptionException(operr( + "primary storage[uuid:%s] does not support encrypted volume", primaryStorageUuid)); + } + } + } + @Override public boolean start() { return true; 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..4fde7bd8014 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java @@ -477,6 +477,7 @@ public void run(FlowTrigger trigger, Map data) { amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); } amsg.setDiskOfferingUuid(self.getDiskOfferingUuid()); + amsg.setEncryptedVolumeAutoAllocation(self.isEncrypted() && msg.getPrimaryStorageUuid() == null); if (msg.getPrimaryStorageUuid() != null) { bus.makeTargetServiceIdByResourceUuid(amsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); 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..817805173a4 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java @@ -352,6 +352,8 @@ public void run(final FlowTrigger trigger, Map data) { amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); amsg.setRequiredPrimaryStorageUuid(msg.getPrimaryStorageUuid()); amsg.setRequiredHostUuid(msg.getHostUuid()); + amsg.setEncryptedVolumeAutoAllocation(Boolean.TRUE.equals(msg.getEncrypted()) + && msg.getPrimaryStorageUuid() == null); if (msg.getApiMsg() != null) { amsg.setSystemTags(msg.getApiMsg().getSystemTags()); } else { diff --git a/test/src/test/java/org/zstack/test/unittest/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtensionTest.java b/test/src/test/java/org/zstack/test/unittest/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtensionTest.java new file mode 100644 index 00000000000..543c392e614 --- /dev/null +++ b/test/src/test/java/org/zstack/test/unittest/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtensionTest.java @@ -0,0 +1,156 @@ +package org.zstack.test.unittest.storage.encrypt; + +import org.junit.Assert; +import org.junit.Test; +import org.zstack.header.storage.primary.PrimaryStorageAllocationSpec; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.vm.DiskAO; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.storage.encrypt.EncryptedVolumePrimaryStorageAllocatorExtension; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class EncryptedVolumePrimaryStorageAllocatorExtensionTest { + private static final String ZHPS_PS_UUID = "zhps-ps-uuid"; + private final EncryptedVolumePrimaryStorageAllocatorExtension extension = new TestExtension(); + + @Test + public void testFilterZhpsForEncryptedDiskWithAutoPrimaryStorage() { + PrimaryStorageAllocationSpec spec = newSpec(true); + List candidates = new ArrayList<>(Arrays.asList(primaryStorage("normal-ps-uuid"), primaryStorage(ZHPS_PS_UUID))); + + extension.filterPrimaryStorageCandidates(spec, candidates); + + Assert.assertEquals(1, candidates.size()); + Assert.assertEquals("normal-ps-uuid", candidates.get(0).getUuid()); + } + + @Test + public void testKeepCandidatesWithoutEncryptedAutoPrimaryStorage() { + PrimaryStorageAllocationSpec spec = newSpec(false); + List candidates = new ArrayList<>(Arrays.asList(primaryStorage("normal-ps-uuid"), primaryStorage(ZHPS_PS_UUID))); + + extension.filterPrimaryStorageCandidates(spec, candidates); + + Assert.assertEquals(2, candidates.size()); + } + + @Test + public void testZhpsDoesNotSupportEncryptedVolume() { + Assert.assertFalse(extension.isPrimaryStorageSupportedForEncryptedVolume(ZHPS_PS_UUID)); + Assert.assertTrue(extension.isPrimaryStorageSupportedForEncryptedVolume("normal-ps-uuid")); + Assert.assertTrue(extension.isPrimaryStorageSupportedForEncryptedVolume(null)); + } + + @Test + public void testKeepDataCandidatesForEncryptedTemplateDiskWithAutoPrimaryStorageInVmSpec() { + VmInstanceSpec spec = new VmInstanceSpec(); + DiskAO disk = DiskAO.nonRootDisk(); + disk.setTemplateUuid("template-uuid"); + disk.setEncrypted(true); + spec.setDeprecatedDisksSpecs(Collections.singletonList(disk)); + List rootPsUuids = new ArrayList<>(Arrays.asList("root-normal-ps-uuid", ZHPS_PS_UUID)); + List dataPsUuids = new ArrayList<>(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID)); + + extension.filterPrimaryStorageCandidates(spec, rootPsUuids, dataPsUuids); + + Assert.assertEquals(Arrays.asList("root-normal-ps-uuid", ZHPS_PS_UUID), rootPsUuids); + Assert.assertEquals(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID), dataPsUuids); + } + + @Test + public void testKeepCandidatesForEncryptedTemplateDiskWithSpecifiedPrimaryStorageInDiskSpec() { + VmInstanceSpec spec = new VmInstanceSpec(); + DiskAO disk = DiskAO.nonRootDisk(); + disk.setTemplateUuid("template-uuid"); + disk.setEncrypted(true); + disk.setPrimaryStorageUuid(ZHPS_PS_UUID); + spec.setDeprecatedDisksSpecs(Collections.singletonList(disk)); + List rootPsUuids = new ArrayList<>(Arrays.asList("root-normal-ps-uuid", ZHPS_PS_UUID)); + List dataPsUuids = new ArrayList<>(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID)); + + extension.filterPrimaryStorageCandidates(spec, rootPsUuids, dataPsUuids); + + Assert.assertEquals(Arrays.asList("root-normal-ps-uuid", ZHPS_PS_UUID), rootPsUuids); + Assert.assertEquals(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID), dataPsUuids); + } + + @Test + public void testKeepDataCandidatesForPlainNonTemplateDataDiskAndEncryptedTemplateDataDisk() { + VmInstanceSpec spec = new VmInstanceSpec(); + DiskAO rootDisk = DiskAO.rootDisk(); + rootDisk.setEncrypted(false); + spec.setRootDisk(rootDisk); + spec.setCandidatePrimaryStorageUuidsForRootVolume(Collections.singletonList(ZHPS_PS_UUID)); + DiskAO plainAutoDisk = DiskAO.nonRootDisk(); + plainAutoDisk.setSize(1024L); + plainAutoDisk.setEncrypted(false); + DiskAO encryptedAutoDisk = DiskAO.nonRootDisk(); + encryptedAutoDisk.setTemplateUuid("encrypted-template-uuid"); + encryptedAutoDisk.setEncrypted(true); + spec.setDeprecatedDisksSpecs(Arrays.asList(plainAutoDisk, encryptedAutoDisk)); + List rootPsUuids = new ArrayList<>(Collections.singletonList(ZHPS_PS_UUID)); + List dataPsUuids = new ArrayList<>(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID)); + + extension.filterPrimaryStorageCandidates(spec, rootPsUuids, dataPsUuids); + + Assert.assertEquals(Collections.singletonList(ZHPS_PS_UUID), rootPsUuids); + Assert.assertEquals(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID), dataPsUuids); + } + + @Test + public void testFilterZhpsForEncryptedNonTemplateDataDiskWithAutoPrimaryStorage() { + VmInstanceSpec spec = new VmInstanceSpec(); + DiskAO encryptedAutoDisk = DiskAO.nonRootDisk(); + encryptedAutoDisk.setSize(1024L); + encryptedAutoDisk.setEncrypted(true); + spec.setDeprecatedDisksSpecs(Collections.singletonList(encryptedAutoDisk)); + List rootPsUuids = new ArrayList<>(Arrays.asList("root-normal-ps-uuid", ZHPS_PS_UUID)); + List dataPsUuids = new ArrayList<>(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID)); + + extension.filterPrimaryStorageCandidates(spec, rootPsUuids, dataPsUuids); + + Assert.assertEquals(Arrays.asList("root-normal-ps-uuid", ZHPS_PS_UUID), rootPsUuids); + Assert.assertEquals(Collections.singletonList("data-normal-ps-uuid"), dataPsUuids); + } + + @Test + public void testFilterZhpsForEncryptedRootDiskWithAutoPrimaryStorageInVmSpec() { + VmInstanceSpec spec = new VmInstanceSpec(); + DiskAO rootDisk = DiskAO.rootDisk(); + rootDisk.setEncrypted(true); + spec.setRootDisk(rootDisk); + List rootPsUuids = new ArrayList<>(Arrays.asList("root-normal-ps-uuid", ZHPS_PS_UUID)); + List dataPsUuids = new ArrayList<>(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID)); + + extension.filterPrimaryStorageCandidates(spec, rootPsUuids, dataPsUuids); + + Assert.assertEquals(Collections.singletonList("root-normal-ps-uuid"), rootPsUuids); + Assert.assertEquals(Arrays.asList("data-normal-ps-uuid", ZHPS_PS_UUID), dataPsUuids); + } + + private PrimaryStorageAllocationSpec newSpec(boolean encryptedAutoAllocation) { + PrimaryStorageAllocationSpec spec = new PrimaryStorageAllocationSpec(); + spec.setEncryptedVolumeAutoAllocation(encryptedAutoAllocation); + return spec; + } + + private PrimaryStorageVO primaryStorage(String uuid) { + PrimaryStorageVO vo = new PrimaryStorageVO(); + vo.setUuid(uuid); + return vo; + } + + private static class TestExtension extends EncryptedVolumePrimaryStorageAllocatorExtension { + @Override + protected List getEncryptedVolumeUnsupportedPrimaryStorageUuids(List psUuids) { + if (psUuids == null || psUuids.contains(ZHPS_PS_UUID)) { + return Collections.singletonList(ZHPS_PS_UUID); + } + return Collections.emptyList(); + } + } +}