From a26b7b56be8ee2ae7cef7e196ae23867f49288c1 Mon Sep 17 00:00:00 2001 From: "zhong.zhou" Date: Mon, 6 Jul 2026 16:37:35 +0800 Subject: [PATCH 1/4] [vm]: ZSV-12607 filter zhps for encrypted auto ps Filter zhps in the allocator extension point. Only applies to encrypted auto primary storage allocation. Test: git diff --check Test: cbok zsv compile --no-deploy passed Test: cbok zsv groovy_test EncryptedVolumePrimaryStorageAllocationCase passed Resolves: ZSV-12607 Change-Id: If4078641c7a42c67552373fcc9d7581cf4f97b1a --- .../VmAllocateHostAndPrimaryStorageFlow.java | 13 + .../vm/VmAllocatePrimaryStorageFlow.java | 29 + ...atePrimaryStorageForAttachingDiskFlow.java | 3 + .../vm/VmInstantiateOtherDiskFlow.java | 6 + conf/springConfigXml/VolumeManager.xml | 9 + .../primary/AllocatePrimaryStorageMsg.java | 8 + .../primary/PrimaryStorageFeature.java | 1 + ...mAllocatePrimaryStorageExtensionPoint.java | 11 + ...olumePrimaryStorageAllocatorExtension.java | 90 ++++ .../VolumeSourceEncryptionResolver.java | 89 +++ .../org/zstack/storage/volume/VolumeBase.java | 3 + .../storage/volume/VolumeManagerImpl.java | 49 +- ...dVolumePrimaryStorageAllocationCase.groovy | 507 ++++++++++++++++++ 13 files changed, 775 insertions(+), 43 deletions(-) create mode 100644 header/src/main/java/org/zstack/header/vm/VmAllocatePrimaryStorageExtensionPoint.java create mode 100644 storage/src/main/java/org/zstack/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtension.java create mode 100644 storage/src/main/java/org/zstack/storage/encrypt/VolumeSourceEncryptionResolver.java create mode 100644 test/src/test/groovy/org/zstack/test/integration/storage/primary/EncryptedVolumePrimaryStorageAllocationCase.groovy 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..5f679ad1bfb 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 (psCombos.isEmpty()) { + 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)); @@ -455,6 +459,7 @@ private List> getPrimaryStorageCombinationFromSpec(VmInstanceSpec s } else { dataPs.add(null); } + filterPrimaryStorageCandidates(spec, rootPs, autoAllocateRootVolumePs, dataPs, autoAllocateDataVolumePs); List> finalPsCombos = new ArrayList<>(); if (autoAllocateRootVolumePs && autoAllocateDataVolumePs) { @@ -478,6 +483,14 @@ private List> getPrimaryStorageCombinationFromSpec(VmInstanceSpec s return finalPsCombos; } + private void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPs, boolean rootAutoAllocation, + List dataPs, boolean dataAutoAllocation) { + for (VmAllocatePrimaryStorageExtensionPoint ext : + pluginRgty.getExtensionList(VmAllocatePrimaryStorageExtensionPoint.class)) { + ext.filterPrimaryStorageCandidates(spec, rootPs, rootAutoAllocation, dataPs, dataAutoAllocation); + } + } + 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..a87f53332d5 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java @@ -7,6 +7,7 @@ import org.zstack.core.asyncbatch.While; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; +import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; import org.zstack.core.errorcode.ErrorFacade; @@ -24,8 +25,10 @@ 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.VmAllocatePrimaryStorageExtensionPoint; import org.zstack.header.vm.VmInstanceConstant; import org.zstack.header.vm.VmInstanceSpec; import org.zstack.header.vm.VmInstanceSpec.VolumeSpec; @@ -58,6 +61,8 @@ public class VmAllocatePrimaryStorageFlow implements Flow { protected ErrorFacade errf; @Autowired protected HostAllocatorManager hostAllocatorMgr; + @Autowired + protected PluginRegistry pluginRgty; @Override public void run(final FlowTrigger trigger, final Map data) { @@ -65,6 +70,13 @@ public void run(final FlowTrigger trigger, final Map data) { final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); HostInventory destHost = spec.getDestHost(); + for (VmAllocatePrimaryStorageExtensionPoint ext : + pluginRgty.getExtensionList(VmAllocatePrimaryStorageExtensionPoint.class)) { + ext.filterPrimaryStorageCandidates(spec, spec.getCandidatePrimaryStorageUuidsForRootVolume(), + isAutoAllocateRootVolumePrimaryStorage(spec), + spec.getCandidatePrimaryStorageUuidsForDataVolume(), false); + } + msgs.add(buildMessageForRootVolume(spec, destHost)); msgs.addAll(buildMessageForDataVolumes(spec, destHost)); @@ -147,6 +159,9 @@ private AllocatePrimaryStorageSpaceMsg buildMessageForRootVolume(final VmInstanc rmsg.setCandidatePrimaryStorageUuids(candidatePs); rmsg.setPossiblePrimaryStorageTypes(selectPsTypesFromSpec(spec)); } + if (disk != null && Boolean.TRUE.equals(disk.getEncrypted()) && isAutoAllocateRootVolumePrimaryStorage(spec)) { + rmsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } Set tags = new HashSet<>(); if (disk != null && disk.getSystemTags() != null) { @@ -181,6 +196,10 @@ private List buildMessageForDataVolumes(final Vm amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); amsg.setDiskOfferingUuid(deprecatedDisk != null ? deprecatedDisk.getDiskOfferingUuid() : null); + if (deprecatedDisk != null && Boolean.TRUE.equals(deprecatedDisk.getEncrypted()) + && isAutoAllocateDataVolumePrimaryStorage(spec, deprecatedDisk)) { + amsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } Set tags = new HashSet<>(); if (spec.getDataVolumeSystemTags() != null) { tags.addAll(spec.getDataVolumeSystemTags()); @@ -197,6 +216,16 @@ private List buildMessageForDataVolumes(final Vm return msgs; } + private boolean isAutoAllocateRootVolumePrimaryStorage(VmInstanceSpec spec) { + DiskAO disk = spec.getRootDisk(); + return (disk == null || disk.getPrimaryStorageUuid() == null) + && isEmpty(spec.getCandidatePrimaryStorageUuidsForRootVolume()); + } + + private boolean isAutoAllocateDataVolumePrimaryStorage(VmInstanceSpec spec, DiskAO disk) { + return disk.getPrimaryStorageUuid() == null && isEmpty(spec.getCandidatePrimaryStorageUuidsForDataVolume()); + } + @Override public void rollback(FlowRollback chain, Map data) { final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); 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..54f76b740db 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()) && diskAO.getPrimaryStorageUuid() == null) { + 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 4d8d6110207..384b6088384 100644 --- a/conf/springConfigXml/VolumeManager.xml +++ b/conf/springConfigXml/VolumeManager.xml @@ -102,6 +102,7 @@ + @@ -134,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..26861a1a693 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 + ? EnumSet.noneOf(PrimaryStorageFeature.class) + : 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..7ac790b3f88 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/VmAllocatePrimaryStorageExtensionPoint.java @@ -0,0 +1,11 @@ +package org.zstack.header.vm; + +import java.util.List; + +public interface VmAllocatePrimaryStorageExtensionPoint { + default void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPrimaryStorageUuids, + boolean rootPrimaryStorageAutoAllocation, + List dataPrimaryStorageUuids, + boolean dataPrimaryStorageAutoAllocation) { + } +} 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..bc051dd7fd7 --- /dev/null +++ b/storage/src/main/java/org/zstack/storage/encrypt/EncryptedVolumePrimaryStorageAllocatorExtension.java @@ -0,0 +1,90 @@ +package org.zstack.storage.encrypt; + +import org.springframework.beans.factory.annotation.Autowired; +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.DiskAO; +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.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(); + @Autowired + private VolumeSourceEncryptionResolver sourceEncryptionResolver; + + @Override + public List allocatePrimaryStorage(Set requiredFeatures, + List candidates) { + if (requiredFeatures != null && candidates != null + && requiredFeatures.contains(PrimaryStorageFeature.ENCRYPTED_VOLUME) + && !candidates.isEmpty()) { + filterEncryptedVolumeUnsupportedPrimaryStorage(candidates); + } + return candidates; + } + + @Override + public void filterPrimaryStorageCandidates(VmInstanceSpec spec, List rootPrimaryStorageUuids, + boolean rootPrimaryStorageAutoAllocation, + List dataPrimaryStorageUuids, + boolean dataPrimaryStorageAutoAllocation) { + sourceEncryptionResolver.resolve(spec); + if (requiresEncryptedRootVolumeAutoPsFilter(spec, rootPrimaryStorageUuids, rootPrimaryStorageAutoAllocation)) { + filterEncryptedVolumeUnsupportedPrimaryStorageUuids(rootPrimaryStorageUuids); + } + if (requiresEncryptedDataVolumeAutoPsFilter(spec, dataPrimaryStorageUuids, dataPrimaryStorageAutoAllocation)) { + filterEncryptedVolumeUnsupportedPrimaryStorageUuids(dataPrimaryStorageUuids); + } + } + + 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 requiresEncryptedRootVolumeAutoPsFilter(VmInstanceSpec spec, List candidates, + boolean autoAllocation) { + return autoAllocation && spec != null && isEncrypted(spec.getRootDisk()) + && candidates != null && !candidates.isEmpty(); + } + + private boolean requiresEncryptedDataVolumeAutoPsFilter(VmInstanceSpec spec, List candidates, + boolean autoAllocation) { + return autoAllocation && spec != null + && spec.getNonTemplateDeprecatedDisksSpecs().stream().anyMatch(this::isEncrypted) + && candidates != null && !candidates.isEmpty(); + } + + private void filterEncryptedVolumeUnsupportedPrimaryStorageUuids(List candidates) { + Set unsupportedPsUuids = new HashSet<>(getEncryptedVolumeUnsupportedPrimaryStorageUuids(candidates)); + candidates.removeIf(unsupportedPsUuids::contains); + } + + 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/encrypt/VolumeSourceEncryptionResolver.java b/storage/src/main/java/org/zstack/storage/encrypt/VolumeSourceEncryptionResolver.java new file mode 100644 index 00000000000..09b1f67803b --- /dev/null +++ b/storage/src/main/java/org/zstack/storage/encrypt/VolumeSourceEncryptionResolver.java @@ -0,0 +1,89 @@ +package org.zstack.storage.encrypt; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.db.Q; +import org.zstack.header.image.ImageConstant; +import org.zstack.header.image.ImageVO; +import org.zstack.header.image.ImageVO_; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO; +import org.zstack.header.storage.snapshot.VolumeSnapshotVO_; +import org.zstack.header.vm.DiskAO; +import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.volume.VolumeVO; +import org.zstack.header.volume.VolumeVO_; + +import java.util.List; + +public class VolumeSourceEncryptionResolver { + @Autowired + private VolumeSnapshotEncryptionHelper snapshotEncryptionHelper; + + public void resolve(VmInstanceSpec spec) { + if (spec == null) { + return; + } + + String rootImageUuid = spec.getImageSpec() == null || spec.getImageSpec().getInventory() == null + ? null : spec.getImageSpec().getInventory().getUuid(); + resolve(spec.getRootDisk(), rootImageUuid); + resolve(spec.getDataDisks()); + } + + public boolean isEncrypted(String imageUuid) { + if (StringUtils.isBlank(imageUuid)) { + return false; + } + + if (snapshotEncryptionHelper.hasTemporarySnapshotImageKey(imageUuid)) { + return true; + } + + String imageUrl = Q.New(ImageVO.class) + .eq(ImageVO_.uuid, imageUuid) + .select(ImageVO_.url) + .findValue(); + if (StringUtils.isBlank(imageUrl)) { + return false; + } + + if (imageUrl.startsWith("volume://")) { + String volumeUuid = imageUrl.substring("volume://".length()); + return Boolean.TRUE.equals(Q.New(VolumeVO.class) + .eq(VolumeVO_.uuid, volumeUuid) + .select(VolumeVO_.encrypted) + .findValue()); + } + + String snapshotUuid; + if (imageUrl.startsWith(ImageConstant.IMAGE_FROM_SNAPSHOT_SCHEMA)) { + snapshotUuid = imageUrl.substring(ImageConstant.IMAGE_FROM_SNAPSHOT_SCHEMA.length()); + } else if (imageUrl.startsWith(ImageConstant.SNAPSHOT_REUSE_IMAGE_SCHEMA)) { + snapshotUuid = imageUrl.substring(ImageConstant.SNAPSHOT_REUSE_IMAGE_SCHEMA.length()); + } else { + return false; + } + snapshotUuid = snapshotUuid.length() >= 32 ? snapshotUuid.substring(0, 32) : snapshotUuid; + return Boolean.TRUE.equals(Q.New(VolumeSnapshotVO.class) + .eq(VolumeSnapshotVO_.uuid, snapshotUuid) + .select(VolumeSnapshotVO_.encrypted) + .findValue()); + } + + private void resolve(List disks) { + if (disks != null) { + disks.forEach(disk -> resolve(disk, null)); + } + } + + private void resolve(DiskAO disk, String defaultImageUuid) { + if (disk == null) { + return; + } + + String imageUuid = StringUtils.defaultIfBlank(disk.getTemplateUuid(), defaultImageUuid); + if (isEncrypted(imageUuid)) { + disk.setEncrypted(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 738bcbe80d8..3359726d52c 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,9 @@ public void run(FlowTrigger trigger, Map data) { amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); } amsg.setDiskOfferingUuid(self.getDiskOfferingUuid()); + if (self.isEncrypted() && msg.getPrimaryStorageUuid() == null) { + amsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } 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 246279580db..caddfff341b 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java @@ -45,6 +45,7 @@ import org.zstack.header.storage.snapshot.group.VolumeSnapshotGroupVO_; import org.zstack.storage.encrypt.VolumeEncryptedResourceKeyBackend; import org.zstack.storage.encrypt.VolumeSnapshotEncryptionHelper; +import org.zstack.storage.encrypt.VolumeSourceEncryptionResolver; import org.zstack.header.vm.*; import org.zstack.header.vm.devices.VmInstanceResourceMetadataManager; import org.zstack.header.volume.*; @@ -100,6 +101,8 @@ public class VolumeManagerImpl extends AbstractService implements VolumeManager, @Autowired private VolumeSnapshotEncryptionHelper snapshotEncryptionHelper; @Autowired + private VolumeSourceEncryptionResolver sourceEncryptionResolver; + @Autowired private VolumeEncryptedResourceKeyBackend volumeEncryptedResourceKeyBackend; private Future volumeExpungeTask; @@ -533,7 +536,7 @@ public void fail(ErrorCode errorCode) { @Override public boolean skip(Map data) { - return !isTemplateFromEncryptedSource(msg.getImageUuid()); + return !sourceEncryptionResolver.isEncrypted(msg.getImageUuid()); } @Override @@ -671,7 +674,7 @@ private VolumeInventory createVolume(CreateVolumeMsg msg) { vo.setType(VolumeType.valueOf(msg.getVolumeType())); vo.setDiskOfferingUuid(msg.getDiskOfferingUuid()); vo.setEncrypted(Boolean.TRUE.equals(msg.getEncrypted()) || - (vo.getType() == VolumeType.Root && isTemplateFromEncryptedSource(msg.getRootImageUuid()))); + (vo.getType() == VolumeType.Root && sourceEncryptionResolver.isEncrypted(msg.getRootImageUuid()))); if (vo.getType() == VolumeType.Root) { vo.setDeviceId(0); } @@ -725,7 +728,7 @@ protected VolumeVO scripts() { } private boolean requiresEncryptInPlace(CreateDataVolumeFromVolumeTemplateMsg msg) { - return Boolean.TRUE.equals(msg.getEncrypted()) && !isTemplateFromEncryptedSource(msg.getImageUuid()); + return Boolean.TRUE.equals(msg.getEncrypted()) && !sourceEncryptionResolver.isEncrypted(msg.getImageUuid()); } private String getHostUuidFromAllocatedInstallUrl(String primaryStorageType, String allocatedInstallUrl) { @@ -782,46 +785,6 @@ public ErrorableValue findConnectedKvmHostByPrimaryStorage(String primar return ErrorableValue.of(host.getUuid()); } - private boolean isTemplateFromEncryptedSource(String imageUuid) { - if (StringUtils.isBlank(imageUuid)) { - return false; - } - - if (snapshotEncryptionHelper.hasTemporarySnapshotImageKey(imageUuid)) { - return true; - } - - String imageUrl = Q.New(ImageVO.class) - .eq(ImageVO_.uuid, imageUuid) - .select(ImageVO_.url) - .findValue(); - if (StringUtils.isBlank(imageUrl)) { - return false; - } - - if (imageUrl.startsWith("volume://")) { - String srcVolumeUuid = imageUrl.substring("volume://".length()); - return Boolean.TRUE.equals(Q.New(VolumeVO.class) - .eq(VolumeVO_.uuid, srcVolumeUuid) - .select(VolumeVO_.encrypted) - .findValue()); - } - - String snapshotUuid; - if (imageUrl.startsWith(ImageConstant.IMAGE_FROM_SNAPSHOT_SCHEMA)) { - snapshotUuid = imageUrl.substring(ImageConstant.IMAGE_FROM_SNAPSHOT_SCHEMA.length()); - } else if (imageUrl.startsWith(ImageConstant.SNAPSHOT_REUSE_IMAGE_SCHEMA)) { - snapshotUuid = imageUrl.substring(ImageConstant.SNAPSHOT_REUSE_IMAGE_SCHEMA.length()); - } else { - return false; - } - snapshotUuid = snapshotUuid.length() >= 32 ? snapshotUuid.substring(0, 32) : snapshotUuid; - return Boolean.TRUE.equals(Q.New(VolumeSnapshotVO.class) - .eq(VolumeSnapshotVO_.uuid, snapshotUuid) - .select(VolumeSnapshotVO_.encrypted) - .findValue()); - } - private void handle(CreateVolumeMsg msg) { VolumeInventory inv = createVolume(msg); CreateVolumeReply reply = new CreateVolumeReply(); 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..9ec9a3f1d61 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/EncryptedVolumePrimaryStorageAllocationCase.groovy @@ -0,0 +1,507 @@ +package org.zstack.test.integration.storage.primary + +import org.zstack.core.cloudbus.CloudBus +import org.zstack.core.Platform +import org.zstack.core.db.DatabaseFacade +import org.zstack.core.db.Q +import org.zstack.core.db.SQL +import org.zstack.header.image.ImageConstant +import org.zstack.header.image.ImageVO +import org.zstack.header.image.ImageVO_ +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO +import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO_ +import org.zstack.header.storage.primary.AllocatePrimaryStorageSpaceMsg +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.storage.snapshot.VolumeSnapshotState +import org.zstack.header.storage.snapshot.VolumeSnapshotStatus +import org.zstack.header.storage.snapshot.VolumeSnapshotTreeStatus +import org.zstack.header.storage.snapshot.VolumeSnapshotTreeVO +import org.zstack.header.storage.snapshot.VolumeSnapshotVO +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.CreateDataVolumeAction +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.sdk.VolumeInventory +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 + VolumeInventory plainZhpsVolume + CloudBus bus + + @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 + bus = bean(CloudBus.class) + + plainZhpsVolume = createPlainVolumeOnZhps() + testFilterZhpsForEncryptedVolumeAutoAllocation() + testFilterZhpsForEncryptedRootImageAutoAllocation() + testFilterZhpsForEncryptedDataDiskAutoAllocation() + testKeepPlainRootExplicitZhpsWhenEncryptedDataDiskAutoAllocation() + testFilterZhpsForEncryptedSource() + testKeepExplicitZhpsForEncryptedSource() + testFilterZhpsForEncryptedTemplateDataVolumeAutoAllocation() + } + } + + void testFilterZhpsForEncryptedVolumeAutoAllocation() { + // 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}" + + AllocatePrimaryStorageSpaceMsg plainMsg = newAllocationMsg() + plainMsg.requiredPrimaryStorageUuid = zhps.uuid + AllocatePrimaryStorageSpaceReply plainReply = bus.call(plainMsg) as AllocatePrimaryStorageSpaceReply + assert plainReply.success : "plain explicit allocation must keep ZHPS available: error=${plainReply.error}" + assert plainReply.primaryStorageInventory.uuid == zhps.uuid : \ + "plain explicit allocation selected an unexpected PS: expected=${zhps.uuid} actual=${plainReply.primaryStorageInventory.uuid}" + releaseAllocation(plainReply) + + AllocatePrimaryStorageSpaceMsg encryptedMsg = newAllocationMsg() + encryptedMsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME) + AllocatePrimaryStorageSpaceReply encryptedReply = bus.call(encryptedMsg) as AllocatePrimaryStorageSpaceReply + + assert encryptedReply.success : "encrypted auto allocation must use a non-ZHPS candidate: error=${encryptedReply.error}" + assert encryptedReply.primaryStorageInventory.uuid == supportedPs.uuid : \ + "encrypted auto allocation selected ZHPS: expected=${supportedPs.uuid} actual=${encryptedReply.primaryStorageInventory.uuid}" + releaseAllocation(encryptedReply) + } + + 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}" + } + + void testFilterZhpsForEncryptedSource() { + ImageInventory image = env.inventoryByName("root-volume-template") as ImageInventory + SQL.New(VolumeVO.class) + .eq(VolumeVO_.uuid, plainZhpsVolume.uuid) + .set(VolumeVO_.encrypted, true) + .update() + + DatabaseFacade dbf = bean(DatabaseFacade.class) + VolumeSnapshotTreeVO tree = new VolumeSnapshotTreeVO() + tree.uuid = Platform.getUuid() + tree.status = VolumeSnapshotTreeStatus.Completed + dbf.persist(tree) + + VolumeSnapshotVO snapshot = new VolumeSnapshotVO() + snapshot.uuid = Platform.getUuid() + snapshot.name = "encrypted-source-snapshot" + snapshot.format = ImageConstant.QCOW2_FORMAT_STRING + snapshot.treeUuid = tree.uuid + snapshot.volumeType = VolumeType.Data.toString() + snapshot.state = VolumeSnapshotState.Enabled + snapshot.status = VolumeSnapshotStatus.Ready + snapshot.encrypted = true + dbf.persist(snapshot) + + try { + assertEncryptedSourceVmCreationUsesSupportedPrimaryStorage( + image.uuid, "volume-source", "volume://${plainZhpsVolume.uuid}") + assertEncryptedSourceVmCreationUsesSupportedPrimaryStorage( + image.uuid, "snapshot-source", "${ImageConstant.IMAGE_FROM_SNAPSHOT_SCHEMA}${snapshot.uuid}") + assertEncryptedSourceVmCreationUsesSupportedPrimaryStorage( + image.uuid, "snapshot-reuse-source", "${ImageConstant.SNAPSHOT_REUSE_IMAGE_SCHEMA}${snapshot.uuid}") + } finally { + SQL.New(ImageVO.class).eq(ImageVO_.uuid, image.uuid) + .set(ImageVO_.url, "http://zstack.org/download/root.qcow2").update() + dbf.remove(snapshot) + dbf.remove(tree) + } + } + + void testKeepExplicitZhpsForEncryptedSource() { + ImageInventory image = env.inventoryByName("root-volume-template") as ImageInventory + SQL.New(VolumeVO.class) + .eq(VolumeVO_.uuid, plainZhpsVolume.uuid) + .set(VolumeVO_.encrypted, true) + .update() + + try { + assertEncryptedSourceVmCreationUsesPrimaryStorage( + image.uuid, "explicit-zhps-volume-source", "volume://${plainZhpsVolume.uuid}", zhps.uuid, zhps.uuid) + } finally { + SQL.New(ImageVO.class).eq(ImageVO_.uuid, image.uuid) + .set(ImageVO_.url, "http://zstack.org/download/root.qcow2").update() + } + } + + private void assertEncryptedSourceVmCreationUsesSupportedPrimaryStorage(String rootImageUuid, String sourceName, + String imageUrl) { + assertEncryptedSourceVmCreationUsesPrimaryStorage(rootImageUuid, sourceName, imageUrl, null, supportedPs.uuid) + } + + private void assertEncryptedSourceVmCreationUsesPrimaryStorage(String rootImageUuid, String sourceName, + String imageUrl, String requestedRootPsUuid, + String expectedRootPsUuid) { + 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 + + SQL.New(ImageVO.class).eq(ImageVO_.uuid, rootImageUuid).set(ImageVO_.url, imageUrl).update() + + String allocatedRootPsUuid = null + def cleanup = notifyWhenReceivedMessage(InstantiateVolumeMsg.class) { InstantiateVolumeMsg msg -> + if (msg instanceof InstantiateRootVolumeMsg) { + allocatedRootPsUuid = msg.primaryStorageUuid + } + } + + try { + expectApiFailure({ + createVmInstance { + name = "vm-from-${sourceName}" + instanceOfferingUuid = offering.uuid + imageUuid = rootImageUuid + zoneUuid = supportedPs.zoneUuid + clusterUuid = cluster.uuid + hostUuid = host.uuid + l3NetworkUuids = [l3.uuid] + defaultL3NetworkUuid = l3.uuid + if (requestedRootPsUuid != null) { + primaryStorageUuidForRootVolume = requestedRootPsUuid + } + } + }) { + assert details.contains("no key provider") : \ + "Encrypted source VM creation failed before encrypted root instantiate: details=${details}" + } + } finally { + cleanup() + } + + assert allocatedRootPsUuid == expectedRootPsUuid : \ + "Encrypted source root volume allocation selected unexpected PS: source=${sourceName} " + + "expected=${expectedRootPsUuid} actual=${allocatedRootPsUuid}" + } + + private VolumeInventory createPlainVolumeOnZhps() { + CreateDataVolumeAction createAction = new CreateDataVolumeAction() + createAction.name = "plain-volume-on-zhps" + createAction.diskSize = SizeUnit.GIGABYTE.toByte(1) + createAction.primaryStorageUuid = zhps.uuid + createAction.encrypted = false + createAction.sessionId = adminSession() + def createResult = createAction.call() + assert createResult.error == null : "failed to create the plain ZHPS volume fixture: ${createResult.error}" + return createResult.value.inventory as VolumeInventory + } + + private AllocatePrimaryStorageSpaceMsg newAllocationMsg() { + AllocatePrimaryStorageSpaceMsg msg = new AllocatePrimaryStorageSpaceMsg() + msg.requiredZoneUuid = zhps.zoneUuid + msg.size = SizeUnit.GIGABYTE.toByte(1) + msg.purpose = PrimaryStorageAllocationPurpose.CreateDataVolume.toString() + bus.makeLocalServiceId(msg, PrimaryStorageConstant.SERVICE_ID) + return msg + } + + private void releaseAllocation(AllocatePrimaryStorageSpaceReply reply) { + ReleasePrimaryStorageSpaceMsg msg = new ReleasePrimaryStorageSpaceMsg() + msg.primaryStorageUuid = reply.primaryStorageInventory.uuid + msg.allocatedInstallUrl = reply.allocatedInstallUrl + msg.diskSize = SizeUnit.GIGABYTE.toByte(1) + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, msg.primaryStorageUuid) + bus.send(msg) + } + + @Override + void clean() { + env.delete() + } +} From 181557c77b806bcb9219f4124632df005ca07107 Mon Sep 17 00:00:00 2001 From: "zhong.zhou" Date: Thu, 16 Jul 2026 18:53:16 +0800 Subject: [PATCH 2/4] [vm]: ZSV-12607 cover encrypted auto ps zhps filtering Add coverage for attaching an uninstantiated encrypted data volume so the attach auto-allocation path filters ZHPS. Test: git diff --check Resolves: ZSV-12607 Change-Id: I1fec349391194e809288b5dac83f74c0 --- ...dVolumePrimaryStorageAllocationCase.groovy | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) 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 index 9ec9a3f1d61..48ca1ea7717 100644 --- 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 @@ -25,6 +25,7 @@ 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.VolumeStatus import org.zstack.header.volume.VolumeType import org.zstack.header.volume.VolumeVO import org.zstack.header.volume.VolumeVO_ @@ -35,6 +36,7 @@ import org.zstack.sdk.ImageInventory import org.zstack.sdk.InstanceOfferingInventory import org.zstack.sdk.L3NetworkInventory import org.zstack.sdk.PrimaryStorageInventory +import org.zstack.sdk.VmInstanceInventory import org.zstack.sdk.VolumeInventory import org.zstack.test.integration.storage.StorageTest import org.zstack.testlib.EnvSpec @@ -151,6 +153,7 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { testFilterZhpsForEncryptedSource() testKeepExplicitZhpsForEncryptedSource() testFilterZhpsForEncryptedTemplateDataVolumeAutoAllocation() + testFilterZhpsWhenAttachingUninstantiatedEncryptedDataVolume() } } @@ -365,6 +368,63 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { "encrypted template data volume was not instantiated on non-ZHPS: NFS download count=${nfsDataVolumeDownloads}" } + void testFilterZhpsWhenAttachingUninstantiatedEncryptedDataVolume() { + 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 + + DiskAO rootDisk = DiskAO.rootDisk() + rootDisk.size = SizeUnit.GIGABYTE.toByte(1) + rootDisk.primaryStorageUuid = supportedPs.uuid + rootDisk.platform = "Linux" + rootDisk.guestOsType = "CentOS" + rootDisk.architecture = "x86_64" + + VmInstanceInventory vm = createVmInstance { + name = "vm-for-attaching-encrypted-data" + instanceOfferingUuid = offering.uuid + zoneUuid = supportedPs.zoneUuid + clusterUuid = cluster.uuid + hostUuid = host.uuid + l3NetworkUuids = [l3.uuid] + defaultL3NetworkUuid = l3.uuid + delegate.diskAOs = [rootDisk] + } as VmInstanceInventory + + VolumeInventory data = createDataVolume { + name = "uninstantiated-encrypted-data" + diskSize = SizeUnit.GIGABYTE.toByte(1) + encrypted = true + } as VolumeInventory + assert data.status == VolumeStatus.NotInstantiated.toString() : \ + "encrypted attach fixture should stay uninstantiated before attach: expected=${VolumeStatus.NotInstantiated} actual=${data.status}" + + String allocatedDataPsUuid = null + def cleanup = notifyWhenReceivedMessage(InstantiateVolumeMsg.class) { InstantiateVolumeMsg msg -> + if (msg.volumeUuid == data.uuid) { + allocatedDataPsUuid = msg.primaryStorageUuid + } + } + + try { + expectApiFailure({ + attachDataVolumeToVm { + vmInstanceUuid = vm.uuid + volumeUuid = data.uuid + } + }) { + assert details.contains("no key provider") : \ + "Encrypted attach should reach encryption after PS allocation: details=${details}" + } + } finally { + cleanup() + } + + assert allocatedDataPsUuid == supportedPs.uuid : \ + "attaching uninstantiated encrypted data volume selected unsupported ZHPS: expected=${supportedPs.uuid} actual=${allocatedDataPsUuid}" + } + void testFilterZhpsForEncryptedSource() { ImageInventory image = env.inventoryByName("root-volume-template") as ImageInventory SQL.New(VolumeVO.class) From 8e6426baaef7f1b45172a5302a14d6807fc99087 Mon Sep 17 00:00:00 2001 From: "zhong.zhou" Date: Thu, 16 Jul 2026 20:03:16 +0800 Subject: [PATCH 3/4] Revert "[vm]: ZSV-12607 cover encrypted auto ps zhps filtering" This reverts commit 181557c77b806bcb9219f4124632df005ca07107. --- ...dVolumePrimaryStorageAllocationCase.groovy | 60 ------------------- 1 file changed, 60 deletions(-) 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 index 48ca1ea7717..9ec9a3f1d61 100644 --- 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 @@ -25,7 +25,6 @@ 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.VolumeStatus import org.zstack.header.volume.VolumeType import org.zstack.header.volume.VolumeVO import org.zstack.header.volume.VolumeVO_ @@ -36,7 +35,6 @@ import org.zstack.sdk.ImageInventory import org.zstack.sdk.InstanceOfferingInventory import org.zstack.sdk.L3NetworkInventory import org.zstack.sdk.PrimaryStorageInventory -import org.zstack.sdk.VmInstanceInventory import org.zstack.sdk.VolumeInventory import org.zstack.test.integration.storage.StorageTest import org.zstack.testlib.EnvSpec @@ -153,7 +151,6 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { testFilterZhpsForEncryptedSource() testKeepExplicitZhpsForEncryptedSource() testFilterZhpsForEncryptedTemplateDataVolumeAutoAllocation() - testFilterZhpsWhenAttachingUninstantiatedEncryptedDataVolume() } } @@ -368,63 +365,6 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { "encrypted template data volume was not instantiated on non-ZHPS: NFS download count=${nfsDataVolumeDownloads}" } - void testFilterZhpsWhenAttachingUninstantiatedEncryptedDataVolume() { - 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 - - DiskAO rootDisk = DiskAO.rootDisk() - rootDisk.size = SizeUnit.GIGABYTE.toByte(1) - rootDisk.primaryStorageUuid = supportedPs.uuid - rootDisk.platform = "Linux" - rootDisk.guestOsType = "CentOS" - rootDisk.architecture = "x86_64" - - VmInstanceInventory vm = createVmInstance { - name = "vm-for-attaching-encrypted-data" - instanceOfferingUuid = offering.uuid - zoneUuid = supportedPs.zoneUuid - clusterUuid = cluster.uuid - hostUuid = host.uuid - l3NetworkUuids = [l3.uuid] - defaultL3NetworkUuid = l3.uuid - delegate.diskAOs = [rootDisk] - } as VmInstanceInventory - - VolumeInventory data = createDataVolume { - name = "uninstantiated-encrypted-data" - diskSize = SizeUnit.GIGABYTE.toByte(1) - encrypted = true - } as VolumeInventory - assert data.status == VolumeStatus.NotInstantiated.toString() : \ - "encrypted attach fixture should stay uninstantiated before attach: expected=${VolumeStatus.NotInstantiated} actual=${data.status}" - - String allocatedDataPsUuid = null - def cleanup = notifyWhenReceivedMessage(InstantiateVolumeMsg.class) { InstantiateVolumeMsg msg -> - if (msg.volumeUuid == data.uuid) { - allocatedDataPsUuid = msg.primaryStorageUuid - } - } - - try { - expectApiFailure({ - attachDataVolumeToVm { - vmInstanceUuid = vm.uuid - volumeUuid = data.uuid - } - }) { - assert details.contains("no key provider") : \ - "Encrypted attach should reach encryption after PS allocation: details=${details}" - } - } finally { - cleanup() - } - - assert allocatedDataPsUuid == supportedPs.uuid : \ - "attaching uninstantiated encrypted data volume selected unsupported ZHPS: expected=${supportedPs.uuid} actual=${allocatedDataPsUuid}" - } - void testFilterZhpsForEncryptedSource() { ImageInventory image = env.inventoryByName("root-volume-template") as ImageInventory SQL.New(VolumeVO.class) From 8dfe8cd14cf9e2d6dd4d0909eb5edaac578d8bba Mon Sep 17 00:00:00 2001 From: "zhong.zhou" Date: Fri, 17 Jul 2026 00:00:43 +0800 Subject: [PATCH 4/4] [vm]: narrow encrypted auto ps filtering Keep ZHPS filtering only for encrypted automatic primary storage allocation in VM creation and uninstantiated volume attach paths. Remove standalone data volume allocation support and explicit-primary-storage test coverage. Test: cbok zsv compile --no-deploy passed Test: git diff --check Resolves: ZSV-12607 Change-Id: Iea5611f5903ce6b32818d5898e7d142069ebb177 --- ...llocateCapacityForAttachingVolumeFlow.java | 3 + .../org/zstack/storage/volume/VolumeBase.java | 3 - ...dVolumePrimaryStorageAllocationCase.groovy | 79 +------------------ 3 files changed, 7 insertions(+), 78 deletions(-) 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..100733db827 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 @@ -95,6 +95,9 @@ public void run(final FlowTrigger trigger, final Map data) { amsg.setVmInstanceUuid(spec.getVmInventory().getUuid()); amsg.setSize(volume.getSize()); amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); + if (Boolean.TRUE.equals(volume.getEncrypted())) { + amsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); + } bus.makeLocalServiceId(amsg, PrimaryStorageConstant.SERVICE_ID); bus.send(amsg, new CloudBusCallBack(trigger) { @Override 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 3359726d52c..738bcbe80d8 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeBase.java @@ -477,9 +477,6 @@ public void run(FlowTrigger trigger, Map data) { amsg.setPurpose(PrimaryStorageAllocationPurpose.CreateDataVolume.toString()); } amsg.setDiskOfferingUuid(self.getDiskOfferingUuid()); - if (self.isEncrypted() && msg.getPrimaryStorageUuid() == null) { - amsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME); - } if (msg.getPrimaryStorageUuid() != null) { bus.makeTargetServiceIdByResourceUuid(amsg, PrimaryStorageConstant.SERVICE_ID, msg.getPrimaryStorageUuid()); 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 index 9ec9a3f1d61..9921349677d 100644 --- 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 @@ -1,6 +1,5 @@ package org.zstack.test.integration.storage.primary -import org.zstack.core.cloudbus.CloudBus import org.zstack.core.Platform import org.zstack.core.db.DatabaseFacade import org.zstack.core.db.Q @@ -10,12 +9,6 @@ import org.zstack.header.image.ImageVO import org.zstack.header.image.ImageVO_ import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO_ -import org.zstack.header.storage.primary.AllocatePrimaryStorageSpaceMsg -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.storage.snapshot.VolumeSnapshotState import org.zstack.header.storage.snapshot.VolumeSnapshotStatus import org.zstack.header.storage.snapshot.VolumeSnapshotTreeStatus @@ -47,7 +40,6 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { PrimaryStorageInventory zhps PrimaryStorageInventory supportedPs VolumeInventory plainZhpsVolume - CloudBus bus @Override void setup() { @@ -141,20 +133,17 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { env.create { zhps = env.inventoryByName("zhps") as PrimaryStorageInventory supportedPs = env.inventoryByName("supported-ps") as PrimaryStorageInventory - bus = bean(CloudBus.class) - plainZhpsVolume = createPlainVolumeOnZhps() - testFilterZhpsForEncryptedVolumeAutoAllocation() + configureZhpsAllocatorMetadata() testFilterZhpsForEncryptedRootImageAutoAllocation() testFilterZhpsForEncryptedDataDiskAutoAllocation() testKeepPlainRootExplicitZhpsWhenEncryptedDataDiskAutoAllocation() testFilterZhpsForEncryptedSource() - testKeepExplicitZhpsForEncryptedSource() testFilterZhpsForEncryptedTemplateDataVolumeAutoAllocation() } } - void testFilterZhpsForEncryptedVolumeAutoAllocation() { + 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) @@ -173,23 +162,6 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { .findValue() assert protocol == VolumeProtocol.Vhost.name() : \ "ZHPS fixture protocol mismatch: expected=${VolumeProtocol.Vhost.name()} actual=${protocol}" - - AllocatePrimaryStorageSpaceMsg plainMsg = newAllocationMsg() - plainMsg.requiredPrimaryStorageUuid = zhps.uuid - AllocatePrimaryStorageSpaceReply plainReply = bus.call(plainMsg) as AllocatePrimaryStorageSpaceReply - assert plainReply.success : "plain explicit allocation must keep ZHPS available: error=${plainReply.error}" - assert plainReply.primaryStorageInventory.uuid == zhps.uuid : \ - "plain explicit allocation selected an unexpected PS: expected=${zhps.uuid} actual=${plainReply.primaryStorageInventory.uuid}" - releaseAllocation(plainReply) - - AllocatePrimaryStorageSpaceMsg encryptedMsg = newAllocationMsg() - encryptedMsg.addRequiredFeature(PrimaryStorageFeature.ENCRYPTED_VOLUME) - AllocatePrimaryStorageSpaceReply encryptedReply = bus.call(encryptedMsg) as AllocatePrimaryStorageSpaceReply - - assert encryptedReply.success : "encrypted auto allocation must use a non-ZHPS candidate: error=${encryptedReply.error}" - assert encryptedReply.primaryStorageInventory.uuid == supportedPs.uuid : \ - "encrypted auto allocation selected ZHPS: expected=${supportedPs.uuid} actual=${encryptedReply.primaryStorageInventory.uuid}" - releaseAllocation(encryptedReply) } void testFilterZhpsForEncryptedRootImageAutoAllocation() { @@ -404,30 +376,8 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { } } - void testKeepExplicitZhpsForEncryptedSource() { - ImageInventory image = env.inventoryByName("root-volume-template") as ImageInventory - SQL.New(VolumeVO.class) - .eq(VolumeVO_.uuid, plainZhpsVolume.uuid) - .set(VolumeVO_.encrypted, true) - .update() - - try { - assertEncryptedSourceVmCreationUsesPrimaryStorage( - image.uuid, "explicit-zhps-volume-source", "volume://${plainZhpsVolume.uuid}", zhps.uuid, zhps.uuid) - } finally { - SQL.New(ImageVO.class).eq(ImageVO_.uuid, image.uuid) - .set(ImageVO_.url, "http://zstack.org/download/root.qcow2").update() - } - } - private void assertEncryptedSourceVmCreationUsesSupportedPrimaryStorage(String rootImageUuid, String sourceName, String imageUrl) { - assertEncryptedSourceVmCreationUsesPrimaryStorage(rootImageUuid, sourceName, imageUrl, null, supportedPs.uuid) - } - - private void assertEncryptedSourceVmCreationUsesPrimaryStorage(String rootImageUuid, String sourceName, - String imageUrl, String requestedRootPsUuid, - String expectedRootPsUuid) { HostInventory host = env.inventoryByName("kvm") as HostInventory ClusterInventory cluster = env.inventoryByName("cluster") as ClusterInventory L3NetworkInventory l3 = env.inventoryByName("l3") as L3NetworkInventory @@ -453,9 +403,6 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { hostUuid = host.uuid l3NetworkUuids = [l3.uuid] defaultL3NetworkUuid = l3.uuid - if (requestedRootPsUuid != null) { - primaryStorageUuidForRootVolume = requestedRootPsUuid - } } }) { assert details.contains("no key provider") : \ @@ -465,9 +412,9 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { cleanup() } - assert allocatedRootPsUuid == expectedRootPsUuid : \ + assert allocatedRootPsUuid == supportedPs.uuid : \ "Encrypted source root volume allocation selected unexpected PS: source=${sourceName} " + - "expected=${expectedRootPsUuid} actual=${allocatedRootPsUuid}" + "expected=${supportedPs.uuid} actual=${allocatedRootPsUuid}" } private VolumeInventory createPlainVolumeOnZhps() { @@ -482,24 +429,6 @@ class EncryptedVolumePrimaryStorageAllocationCase extends SubCase { return createResult.value.inventory as VolumeInventory } - private AllocatePrimaryStorageSpaceMsg newAllocationMsg() { - AllocatePrimaryStorageSpaceMsg msg = new AllocatePrimaryStorageSpaceMsg() - msg.requiredZoneUuid = zhps.zoneUuid - msg.size = SizeUnit.GIGABYTE.toByte(1) - msg.purpose = PrimaryStorageAllocationPurpose.CreateDataVolume.toString() - bus.makeLocalServiceId(msg, PrimaryStorageConstant.SERVICE_ID) - return msg - } - - private void releaseAllocation(AllocatePrimaryStorageSpaceReply reply) { - ReleasePrimaryStorageSpaceMsg msg = new ReleasePrimaryStorageSpaceMsg() - msg.primaryStorageUuid = reply.primaryStorageInventory.uuid - msg.allocatedInstallUrl = reply.allocatedInstallUrl - msg.diskSize = SizeUnit.GIGABYTE.toByte(1) - bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, msg.primaryStorageUuid) - bus.send(msg) - } - @Override void clean() { env.delete()