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..5cf9637d262 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostAndPrimaryStorageFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostAndPrimaryStorageFlow.java @@ -455,6 +455,7 @@ private List> getPrimaryStorageCombinationFromSpec(VmInstanceSpec s } else { dataPs.add(null); } + filterPrimaryStorageCandidates(spec, rootPs, autoAllocateRootVolumePs); List> finalPsCombos = new ArrayList<>(); if (autoAllocateRootVolumePs && autoAllocateDataVolumePs) { @@ -478,6 +479,13 @@ private List> getPrimaryStorageCombinationFromSpec(VmInstanceSpec s return finalPsCombos; } + private void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPs, boolean rootAutoAllocation) { + for (VmAllocatePrimaryStorageExtensionPoint ext : + pluginRgty.getExtensionList(VmAllocatePrimaryStorageExtensionPoint.class)) { + ext.filterPrimaryStorageCandidates(spec, rootPs, rootAutoAllocation); + } + } + 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..1f81aa8f918 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java @@ -24,6 +24,7 @@ import org.zstack.header.storage.primary.AllocatePrimaryStorageSpaceReply; import org.zstack.header.storage.primary.PrimaryStorageAllocationPurpose; import org.zstack.header.storage.primary.PrimaryStorageConstant; +import org.zstack.header.storage.primary.PrimaryStorageFeature; import org.zstack.header.storage.primary.ReleasePrimaryStorageSpaceMsg; import org.zstack.header.vm.DiskAO; import org.zstack.header.vm.VmInstanceConstant; @@ -147,6 +148,10 @@ private AllocatePrimaryStorageSpaceMsg buildMessageForRootVolume(final VmInstanc rmsg.setCandidatePrimaryStorageUuids(candidatePs); rmsg.setPossiblePrimaryStorageTypes(selectPsTypesFromSpec(spec)); } + if (disk != null && Boolean.TRUE.equals(disk.getEncrypted()) + && disk.getPrimaryStorageUuid() == null && isEmpty(candidatePs)) { + rmsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } Set tags = new HashSet<>(); if (disk != null && disk.getSystemTags() != 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..8e70361df06 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageForAttachingDiskFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageForAttachingDiskFlow.java @@ -63,6 +63,9 @@ public void run(final FlowTrigger chain, final Map data) { } else { amsg.setRequiredHostUuid(hinv.getUuid()); } + if (Boolean.TRUE.equals(volume.getEncrypted())) { + amsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } bus.send(amsg, new CloudBusCallBack(chain) { @Override 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 4d5a9eea5a1..8ac67cffcd6 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateOtherDiskFlow.java @@ -130,6 +130,9 @@ public void run(final FlowTrigger innerTrigger, Map data) { amsg.setRequiredPrimaryStorageUuid(diskAO.getPrimaryStorageUuid()); amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); amsg.setTags(diskAO.getSystemTags()); + if (Boolean.TRUE.equals(diskAO.getEncrypted()) && diskAO.getPrimaryStorageUuid() == null) { + amsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } bus.makeLocalServiceId(amsg, PrimaryStorageConstant.SERVICE_ID); bus.send(amsg, new CloudBusCallBack(innerTrigger) { @Override @@ -280,6 +283,9 @@ public void run(final FlowTrigger innerTrigger, Map data) { List bsUuids = template.getBackupStorageRefs().stream() .map(ImageBackupStorageRefVO::getBackupStorageUuid).collect(Collectors.toList()); amsg.setPossiblePrimaryStorageTypes(possiblePrimaryStorageTypes(bsUuids)); + if (Boolean.TRUE.equals(diskAO.getEncrypted())) { + amsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } 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 cbdab5a0d4b..704b62fb83b 100644 --- a/conf/springConfigXml/VolumeManager.xml +++ b/conf/springConfigXml/VolumeManager.xml @@ -135,6 +135,14 @@ + + + + + + + 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..c84254563ad 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 @@ -5,6 +5,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -55,6 +56,13 @@ public void setRequiredFeatures(Set requiredFeatures) { this.requiredFeatures = requiredFeatures; } + public void addRequiredFeature(PrimaryStorageFeature requiredFeature) { + requiredFeatures = requiredFeatures == null || requiredFeatures.isEmpty() + ? EnumSet.of(requiredFeature) + : EnumSet.copyOf(requiredFeatures); + requiredFeatures.add(requiredFeature); + } + public List getExcludePrimaryStorageTypes() { return excludePrimaryStorageTypes; } diff --git a/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageFeature.java b/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageFeature.java index 1dbee35303c..91e584996a1 100644 --- a/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageFeature.java +++ b/header/src/main/java/org/zstack/header/storage/primary/PrimaryStorageFeature.java @@ -6,4 +6,5 @@ */ public enum PrimaryStorageFeature { SHARED_VOLUME, + ENCRYPTED_VOLUME, } 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..b98c2737506 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmAllocatePrimaryStorageExtensionPoint.java @@ -0,0 +1,8 @@ +package org.zstack.header.vm; + +import java.util.List; + +public interface VmAllocatePrimaryStorageExtensionPoint { + void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPrimaryStorageUuids, + boolean rootPrimaryStorageAutoAllocation); +} diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocateCapacityForAttachingVolumeFlow.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocateCapacityForAttachingVolumeFlow.java index 0faf93884cb..f7a32c1b3b3 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocateCapacityForAttachingVolumeFlow.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocateCapacityForAttachingVolumeFlow.java @@ -86,6 +86,9 @@ public void run(final FlowTrigger trigger, final Map data) { if (localStorageHostRefVOList != null && !localStorageHostRefVOList.isEmpty()) { localStorageHostRefVOList.forEach(r -> amsg.addExcludePrimaryStorageUuid(r.getPrimaryStorageUuid())); } + if (Boolean.TRUE.equals(volume.getEncrypted())) { + amsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } } else { amsg.setAllocationStrategy(LocalStorageConstants.LOCAL_STORAGE_ALLOCATOR_STRATEGY); amsg.setRequiredPrimaryStorageUuid(spec.getVmInventory().getRootVolume().getPrimaryStorageUuid()); 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..e44453d685b --- /dev/null +++ b/storage/src/main/java/org/zstack/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtension.java @@ -0,0 +1,63 @@ +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.PrimaryStorageFeature; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.vm.VmAllocatePrimaryStorageExtensionPoint; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.volume.VolumeProtocol; +import org.zstack.storage.primary.PrimaryStorageFeatureAllocatorExtensionPoint; + +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, + PrimaryStorageFeatureAllocatorExtensionPoint { + private static final String ZHPS_PRIMARY_STORAGE_IDENTITY = "expon"; + private static final String ZHPS_PRIMARY_STORAGE_PROTOCOL = VolumeProtocol.Vhost.name(); + + @Override + public List allocatePrimaryStorage(Set requiredFeatures, + List candidates) { + if (!requiredFeatures.contains(PrimaryStorageFeature.ENCRYPTED_VOLUME) || candidates.isEmpty()) { + return candidates; + } + + List psUuids = candidates.stream().map(PrimaryStorageVO::getUuid).collect(Collectors.toList()); + Set unsupportedPsUuids = new HashSet<>(getEncryptedVolumeUnsupportedPrimaryStorageUuids(psUuids)); + candidates.removeIf(ps -> unsupportedPsUuids.contains(ps.getUuid())); + return candidates; + } + + @Override + public void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPrimaryStorageUuids, + boolean rootPrimaryStorageAutoAllocation) { + if (!rootPrimaryStorageAutoAllocation || spec.getRootDisk() == null + || !Boolean.TRUE.equals(spec.getRootDisk().getEncrypted()) || rootPrimaryStorageUuids.isEmpty()) { + return; + } + + Set unsupportedPsUuids = new HashSet<>( + getEncryptedVolumeUnsupportedPrimaryStorageUuids(rootPrimaryStorageUuids)); + rootPrimaryStorageUuids.removeIf(unsupportedPsUuids::contains); + } + + public boolean isEncryptedVolumeUnsupportedPrimaryStorage(String psUuid) { + return psUuid != null && !getEncryptedVolumeUnsupportedPrimaryStorageUuids( + Collections.singletonList(psUuid)).isEmpty(); + } + + private List getEncryptedVolumeUnsupportedPrimaryStorageUuids(List psUuids) { + return Q.New(ExternalPrimaryStorageVO.class) + .select(ExternalPrimaryStorageVO_.uuid) + .eq(ExternalPrimaryStorageVO_.identity, ZHPS_PRIMARY_STORAGE_IDENTITY) + .eq(ExternalPrimaryStorageVO_.defaultProtocol, ZHPS_PRIMARY_STORAGE_PROTOCOL) + .in(ExternalPrimaryStorageVO_.uuid, psUuids) + .listValues(); + } +} 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..ff60475d074 100755 --- a/storage/src/main/java/org/zstack/storage/volume/InstantiateVolumeForNewCreatedVmExtension.java +++ b/storage/src/main/java/org/zstack/storage/volume/InstantiateVolumeForNewCreatedVmExtension.java @@ -22,6 +22,7 @@ import org.zstack.header.vm.VmInstanceSpec.ImageSpec; import org.zstack.header.volume.*; import org.zstack.identity.AccountManager; +import org.zstack.storage.encrypt.EncryptedVolumePrimaryStorageAllocatorExtension; import org.zstack.utils.Utils; import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; @@ -31,6 +32,8 @@ import java.net.URISyntaxException; import java.util.*; +import static org.zstack.core.Platform.operr; + public class InstantiateVolumeForNewCreatedVmExtension implements PreVmInstantiateResourceExtensionPoint { private static final CLogger logger = Utils.getLogger(InstantiateVolumeForNewCreatedVmExtension.class); @@ -42,6 +45,8 @@ public class InstantiateVolumeForNewCreatedVmExtension implements PreVmInstantia private PluginRegistry pluginRgty; @Autowired protected AccountManager acntMgr; + @Autowired + private EncryptedVolumePrimaryStorageAllocatorExtension encryptedVolumePrimaryStorageAllocatorExtension; @Override public void preBeforeInstantiateVmResource(VmInstanceSpec spec) throws VmInstantiateResourceException { @@ -246,6 +251,12 @@ public void preInstantiateVmResource(VmInstanceSpec spec, Completion completion) for (DiskAO diskAO : templateDataDisks) { String psUuid = diskAO.getPrimaryStorageUuid() != null ? diskAO.getPrimaryStorageUuid() : defaultPsUuid; + if (diskAO.getPrimaryStorageUuid() == null && Boolean.TRUE.equals(diskAO.getEncrypted()) + && encryptedVolumePrimaryStorageAllocatorExtension + .isEncryptedVolumeUnsupportedPrimaryStorage(psUuid)) { + completion.fail(operr("no primary storage candidate is available")); + return; + } msgs.add(buildCreateDataVolumeFromTemplateMsg(spec, diskAO.getTemplateUuid(), psUuid, diskAO.getSystemTags(), accountUuid, diskAO.getEncrypted())); } diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/EncryptedVolumePrimaryStorageAllocationCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/EncryptedVolumePrimaryStorageAllocationCase.groovy new file mode 100644 index 00000000000..338811d0924 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/EncryptedVolumePrimaryStorageAllocationCase.groovy @@ -0,0 +1,330 @@ +package org.zstack.test.integration.storage.primary + +import org.zstack.core.db.Q +import org.zstack.core.db.SQL +import org.zstack.header.image.ImageConstant +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO_ +import org.zstack.header.vm.DiskAO +import org.zstack.header.volume.InstantiateRootVolumeMsg +import org.zstack.header.volume.InstantiateVolumeMsg +import org.zstack.header.volume.VolumeProtocol +import org.zstack.header.volume.VolumeType +import org.zstack.header.volume.VolumeVO +import org.zstack.header.volume.VolumeVO_ +import org.zstack.sdk.ClusterInventory +import org.zstack.sdk.HostInventory +import org.zstack.sdk.ImageInventory +import org.zstack.sdk.InstanceOfferingInventory +import org.zstack.sdk.L3NetworkInventory +import org.zstack.sdk.PrimaryStorageInventory +import org.zstack.test.integration.storage.StorageTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.SubCase +import org.zstack.utils.data.SizeUnit +import org.zstack.storage.primary.nfs.NfsPrimaryToSftpBackupKVMBackend + +class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { + EnvSpec env + PrimaryStorageInventory zhps + PrimaryStorageInventory supportedPs + + @Override + void setup() { + useSpring(StorageTest.springSpec) + } + + @Override + void environment() { + env = makeEnv { + instanceOffering { + name = "instance-offering" + memory = SizeUnit.GIGABYTE.toByte(2) + cpu = 2 + } + + sftpBackupStorage { + name = "sftp" + url = "/sftp" + username = "root" + password = "password" + hostname = "localhost" + + image { + name = "data-volume-template" + url = "http://zstack.org/download/data.qcow2" + mediaType = ImageConstant.ImageMediaType.DataVolumeTemplate.toString() + } + + image { + name = "root-volume-template" + url = "http://zstack.org/download/root.qcow2" + mediaType = ImageConstant.ImageMediaType.RootVolumeTemplate.toString() + } + } + + zone { + name = "zone" + + cluster { + name = "cluster" + hypervisorType = "KVM" + + kvm { + name = "kvm" + managementIp = "127.0.0.1" + username = "root" + password = "password" + } + + attachPrimaryStorage("zhps") + attachPrimaryStorage("supported-ps") + attachL2Network("l2") + } + + externalPrimaryStorage { + name = "zhps" + identity = "zbs" + defaultOutputProtocol = "CBD" + config = '{"mdsUrls":["root:password@127.0.1.1","root:password@127.0.1.2","root:password@127.0.1.3"],"logicalPoolName":"lpool1"}' + url = "fake-url" + } + + nfsPrimaryStorage { + name = "supported-ps" + url = "127.0.0.1:/nfs" + } + + l2NoVlanNetwork { + name = "l2" + physicalInterface = "eth0" + + l3Network { + name = "l3" + + ip { + startIp = "192.168.100.10" + endIp = "192.168.100.20" + netmask = "255.255.255.0" + gateway = "192.168.100.1" + } + } + } + + attachBackupStorage("sftp") + } + } + } + + @Override + void test() { + env.create { + zhps = env.inventoryByName("zhps") as PrimaryStorageInventory + supportedPs = env.inventoryByName("supported-ps") as PrimaryStorageInventory + configureZhpsAllocatorMetadata() + testFilterZhpsForEncryptedRootImageAutoAllocation() + testFilterZhpsForEncryptedDataDiskAutoAllocation() + testKeepPlainRootExplicitZhpsWhenEncryptedDataDiskAutoAllocation() + testFilterZhpsForEncryptedTemplateDataVolumeAutoAllocation() + } + } + + private void configureZhpsAllocatorMetadata() { + // Expon tests require physical storage, so reuse the simulated external PS and change allocator-visible metadata only. + SQL.New(ExternalPrimaryStorageVO.class) + .eq(ExternalPrimaryStorageVO_.uuid, zhps.uuid) + .set(ExternalPrimaryStorageVO_.identity, "expon") + .set(ExternalPrimaryStorageVO_.defaultProtocol, VolumeProtocol.Vhost.name()) + .update() + + String identity = Q.New(ExternalPrimaryStorageVO.class) + .select(ExternalPrimaryStorageVO_.identity) + .eq(ExternalPrimaryStorageVO_.uuid, zhps.uuid) + .findValue() + assert identity == "expon" : "ZHPS fixture identity mismatch: expected=expon actual=${identity}" + String protocol = Q.New(ExternalPrimaryStorageVO.class) + .select(ExternalPrimaryStorageVO_.defaultProtocol) + .eq(ExternalPrimaryStorageVO_.uuid, zhps.uuid) + .findValue() + assert protocol == VolumeProtocol.Vhost.name() : \ + "ZHPS fixture protocol mismatch: expected=${VolumeProtocol.Vhost.name()} actual=${protocol}" + } + + void testFilterZhpsForEncryptedRootImageAutoAllocation() { + ImageInventory image = env.inventoryByName("root-volume-template") as ImageInventory + String allocatedRootPsUuid = null + + def cleanup = notifyWhenReceivedMessage(InstantiateVolumeMsg.class) { InstantiateVolumeMsg msg -> + if (msg instanceof InstantiateRootVolumeMsg) { + allocatedRootPsUuid = msg.primaryStorageUuid + } + } + + try { + DiskAO rootDisk = DiskAO.rootDisk() + rootDisk.encrypted = true + expectApiFailure({ + createVmInstance { + name = "vm-with-encrypted-root-image" + instanceOfferingUuid = (env.inventoryByName("instance-offering") as InstanceOfferingInventory).uuid + imageUuid = image.uuid + zoneUuid = supportedPs.zoneUuid + clusterUuid = (env.inventoryByName("cluster") as ClusterInventory).uuid + hostUuid = (env.inventoryByName("kvm") as HostInventory).uuid + l3NetworkUuids = [(env.inventoryByName("l3") as L3NetworkInventory).uuid] + defaultL3NetworkUuid = (env.inventoryByName("l3") as L3NetworkInventory).uuid + delegate.diskAOs = [rootDisk] + } + }) { + assert details.contains("no key provider") : \ + "Encrypted root image VM creation should reach encryption after PS allocation: details=${details}" + } + } finally { + cleanup() + } + + assert allocatedRootPsUuid == supportedPs.uuid : \ + "encrypted root image auto allocation selected unsupported ZHPS: expected=${supportedPs.uuid} actual=${allocatedRootPsUuid}" + } + + void testFilterZhpsForEncryptedDataDiskAutoAllocation() { + String allocatedDataPsUuid = null + + def cleanup = notifyWhenReceivedMessage(InstantiateVolumeMsg.class) { InstantiateVolumeMsg msg -> + if (!(msg instanceof InstantiateRootVolumeMsg)) { + allocatedDataPsUuid = msg.primaryStorageUuid + } + } + + try { + DiskAO rootDisk = DiskAO.rootDisk() + rootDisk.size = SizeUnit.GIGABYTE.toByte(1) + rootDisk.primaryStorageUuid = supportedPs.uuid + rootDisk.platform = "Linux" + rootDisk.guestOsType = "CentOS" + rootDisk.architecture = "x86_64" + DiskAO dataDisk = DiskAO.nonRootDisk() + dataDisk.size = SizeUnit.GIGABYTE.toByte(1) + dataDisk.encrypted = true + + expectApiFailure({ + createVmInstance { + name = "vm-with-encrypted-data-disk" + instanceOfferingUuid = (env.inventoryByName("instance-offering") as InstanceOfferingInventory).uuid + zoneUuid = supportedPs.zoneUuid + clusterUuid = (env.inventoryByName("cluster") as ClusterInventory).uuid + hostUuid = (env.inventoryByName("kvm") as HostInventory).uuid + l3NetworkUuids = [(env.inventoryByName("l3") as L3NetworkInventory).uuid] + defaultL3NetworkUuid = (env.inventoryByName("l3") as L3NetworkInventory).uuid + delegate.diskAOs = [rootDisk, dataDisk] + } + }) { + assert details.contains("no key provider") : \ + "Encrypted data disk VM creation should reach encryption after PS allocation: details=${details}" + } + } finally { + cleanup() + } + + assert allocatedDataPsUuid == supportedPs.uuid : \ + "encrypted data disk auto allocation selected unsupported ZHPS: expected=${supportedPs.uuid} actual=${allocatedDataPsUuid}" + } + + void testKeepPlainRootExplicitZhpsWhenEncryptedDataDiskAutoAllocation() { + String allocatedRootPsUuid = null + String allocatedDataPsUuid = null + + def cleanup = notifyWhenReceivedMessage(InstantiateVolumeMsg.class) { InstantiateVolumeMsg msg -> + VolumeType volumeType = Q.New(VolumeVO.class) + .select(VolumeVO_.type) + .eq(VolumeVO_.uuid, msg.volumeUuid) + .findValue() + if (volumeType == VolumeType.Root) { + allocatedRootPsUuid = msg.primaryStorageUuid + } else if (volumeType == VolumeType.Data) { + allocatedDataPsUuid = msg.primaryStorageUuid + } + } + + try { + DiskAO rootDisk = DiskAO.rootDisk() + rootDisk.size = SizeUnit.GIGABYTE.toByte(1) + rootDisk.primaryStorageUuid = zhps.uuid + rootDisk.platform = "Linux" + rootDisk.guestOsType = "CentOS" + rootDisk.architecture = "x86_64" + DiskAO dataDisk = DiskAO.nonRootDisk() + dataDisk.size = SizeUnit.GIGABYTE.toByte(1) + dataDisk.encrypted = true + + expectApiFailure({ + createVmInstance { + name = "vm-with-plain-root-on-zhps-and-encrypted-data" + instanceOfferingUuid = (env.inventoryByName("instance-offering") as InstanceOfferingInventory).uuid + zoneUuid = supportedPs.zoneUuid + clusterUuid = (env.inventoryByName("cluster") as ClusterInventory).uuid + hostUuid = (env.inventoryByName("kvm") as HostInventory).uuid + l3NetworkUuids = [(env.inventoryByName("l3") as L3NetworkInventory).uuid] + defaultL3NetworkUuid = (env.inventoryByName("l3") as L3NetworkInventory).uuid + delegate.diskAOs = [rootDisk, dataDisk] + } + }) { + assert details.contains("no key provider") : \ + "Encrypted data disk VM creation should keep explicit plain root PS and reach encryption: details=${details}" + } + } finally { + cleanup() + } + + assert allocatedRootPsUuid == zhps.uuid : \ + "plain root with explicit ZHPS should not be filtered by encrypted data disk: expected=${zhps.uuid} actual=${allocatedRootPsUuid}" + assert allocatedDataPsUuid == supportedPs.uuid : \ + "encrypted data disk auto allocation should filter ZHPS independently: expected=${supportedPs.uuid} actual=${allocatedDataPsUuid}" + } + + void testFilterZhpsForEncryptedTemplateDataVolumeAutoAllocation() { + HostInventory host = env.inventoryByName("kvm") as HostInventory + ClusterInventory cluster = env.inventoryByName("cluster") as ClusterInventory + L3NetworkInventory l3 = env.inventoryByName("l3") as L3NetworkInventory + InstanceOfferingInventory offering = env.inventoryByName("instance-offering") as InstanceOfferingInventory + ImageInventory dataVolumeTemplate = env.inventoryByName("data-volume-template") as ImageInventory + + DiskAO rootDisk = DiskAO.rootDisk() + rootDisk.size = SizeUnit.GIGABYTE.toByte(1) + rootDisk.primaryStorageUuid = supportedPs.uuid + rootDisk.platform = "Linux" + rootDisk.guestOsType = "CentOS" + rootDisk.architecture = "x86_64" + DiskAO dataDisk = DiskAO.nonRootDisk().withImage(dataVolumeTemplate.uuid) + dataDisk.encrypted = true + + int nfsDataVolumeDownloads = 0 + env.afterSimulator(NfsPrimaryToSftpBackupKVMBackend.DOWNLOAD_FROM_SFTP_PATH) { rsp, e -> + nfsDataVolumeDownloads++ + return rsp + } + + expectApiFailure({ + createVmInstance { + name = "vm-with-encrypted-template-data-volume" + instanceOfferingUuid = offering.uuid + zoneUuid = supportedPs.zoneUuid + clusterUuid = cluster.uuid + hostUuid = host.uuid + l3NetworkUuids = [l3.uuid] + defaultL3NetworkUuid = l3.uuid + delegate.diskAOs = [rootDisk, dataDisk] + } + }) { + assert details.contains("no key provider bound and no default key provider configured") + } + + assert nfsDataVolumeDownloads == 1 : \ + "encrypted template data volume was not instantiated on non-ZHPS: NFS download count=${nfsDataVolumeDownloads}" + } + + @Override + void clean() { + env.delete() + } +}