From 5b9dc6f5c5e1940fd157ca9efaf28b8a991685fc Mon Sep 17 00:00:00 2001 From: J M Date: Tue, 7 Jul 2026 11:13:46 +0800 Subject: [PATCH] [storage]: reject Legacy-BIOS image on 4096-sector primary storage A vm created with a ZBS-vhost root volume reached Running but SeaBIOS reported "No bootable device", because the ZBS-vhost bdev exposes a 4096-byte logical block size on which traditional 512-sector BIOS/MBR (Legacy, non-UEFI) images cannot boot. Replace the hardcoded VmInstanceApiInterceptor guard with a layered allocator filter reusing the existing requiredFeatures pipeline: - StorageCapabilities gains minLogicalSectorSize (default 512); the ZBS controller reports 4096 when its default protocol is Vhost. - Add PrimaryStorageFeature.LEGACY_BOOT. VmAllocatePrimaryStorageFlow detects a Legacy-boot image (via image bootMode system tag) and sets the feature on the allocate msg. - ExternalPrimaryStorageFactory filters out candidates whose reported minLogicalSectorSize exceeds 512 when LEGACY_BOOT is required, so a Legacy image can no longer land on a ZBS-vhost primary storage. Resolves: ZSTAC-86547 Change-Id: If64d639597498884a084108ad76f664b923e158f --- .../vm/VmAllocatePrimaryStorageFlow.java | 15 ++ .../addon/primary/StorageCapabilities.java | 9 + .../primary/PrimaryStorageAllocationSpec.java | 9 + .../primary/PrimaryStorageFeature.java | 1 + .../storage/zbs/ZbsStorageController.java | 1 + .../ExternalPrimaryStorageFactory.java | 34 ++- ...StorageFeatureAllocatorExtensionPoint.java | 2 +- .../PrimaryStorageFeatureAllocatorFlow.java | 2 +- .../primary/PrimaryStorageManagerImpl.java | 12 +- .../zbs/ZbsVhostLegacyBootAllocateCase.groovy | 210 ++++++++++++++++++ .../addon/zbs/ZbsVhostVolumeCase.groovy | 6 + 11 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsVhostLegacyBootAllocateCase.groovy 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 3fe184ac48d..e407f547914 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocatePrimaryStorageFlow.java @@ -19,6 +19,8 @@ import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.host.HostInventory; import org.zstack.header.image.ImageInventory; +import org.zstack.header.image.ImageBootMode; +import org.zstack.header.image.ImageVO; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.backup.BackupStorageVO; import org.zstack.header.storage.backup.BackupStorageVO_; @@ -26,6 +28,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.VmInstanceConstant; import org.zstack.header.vm.VmInstanceSpec; @@ -37,6 +40,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -80,6 +84,9 @@ public void run(final FlowTrigger trigger, final Map data) { rmsg.setPurpose(PrimaryStorageAllocationPurpose.CreateNewVm.toString()); rmsg.setPossiblePrimaryStorageTypes(selectPsTypesFromSpec(spec)); rmsg.setSystemTags(spec.getRootVolumeSystemTags()); + if (isLegacyBootImage(rmsg.getImageUuid())) { + rmsg.setRequiredFeatures(Collections.singleton(PrimaryStorageFeature.LEGACY_BOOT)); + } bus.makeLocalServiceId(rmsg, PrimaryStorageConstant.SERVICE_ID); msgs.add(rmsg); @@ -170,6 +177,14 @@ public void rollback(FlowRollback chain, Map data) { chain.rollback(); } + private boolean isLegacyBootImage(String imageUuid) { + if (imageUuid == null) { + return false; + } + String bootMode = VmSystemTags.BOOT_MODE.getTokenByResourceUuid(imageUuid, ImageVO.class, VmSystemTags.BOOT_MODE_TOKEN); + return bootMode == null || bootMode.equalsIgnoreCase(ImageBootMode.Legacy.name()); + } + private List selectPsTypesFromSpec(final VmInstanceSpec spec) { // get ps types from image bs and cdroms bs List psTypes = null; diff --git a/header/src/main/java/org/zstack/header/storage/addon/primary/StorageCapabilities.java b/header/src/main/java/org/zstack/header/storage/addon/primary/StorageCapabilities.java index e1ca8e8e5c3..b4ce7a58897 100644 --- a/header/src/main/java/org/zstack/header/storage/addon/primary/StorageCapabilities.java +++ b/header/src/main/java/org/zstack/header/storage/addon/primary/StorageCapabilities.java @@ -19,6 +19,15 @@ public class StorageCapabilities { public List supportedImageFormats; private VolumeProtocol defaultIsoActiveProtocol; private VolumeProtocol defaultImageExportProtocol; + private int minLogicalSectorSize = 512; + + public int getMinLogicalSectorSize() { + return minLogicalSectorSize; + } + + public void setMinLogicalSectorSize(int minLogicalSectorSize) { + this.minLogicalSectorSize = minLogicalSectorSize; + } public boolean isSupportShareableVolume() { return supportShareableVolume; 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..620aa82d17e 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,15 @@ public class PrimaryStorageAllocationSpec { private List excludePrimaryStorageTypes; private String backupStorageUuid; private Set requiredFeatures; + private String requiredProtocol; + + public String getRequiredProtocol() { + return requiredProtocol; + } + + public void setRequiredProtocol(String requiredProtocol) { + this.requiredProtocol = requiredProtocol; + } public Set getRequiredFeatures() { return requiredFeatures; 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..7c5afef507a 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, + LEGACY_BOOT, } diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java index 054dfd572a0..8c85396dd58 100644 --- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java +++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java @@ -142,6 +142,7 @@ public class ZbsStorageController implements PrimaryStorageControllerSvc, Primar capabilities.setSupportedImageFormats(Collections.singletonList(ImageConstant.RAW_FORMAT_STRING)); capabilities.setDefaultIsoActiveProtocol(VolumeProtocol.CBD); capabilities.setDefaultImageExportProtocol(VolumeProtocol.NBD); + capabilities.setMinLogicalSectorSize(4096); } @Override diff --git a/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorageFactory.java b/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorageFactory.java index 3a471d74589..724f48fc55e 100644 --- a/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorageFactory.java +++ b/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorageFactory.java @@ -41,6 +41,7 @@ import org.zstack.header.vm.cdrom.VmCdRomVO_; import org.zstack.header.volume.VolumeDeletionPolicyManager; import org.zstack.header.volume.VolumeInventory; +import org.zstack.header.volume.VolumeProtocol; import org.zstack.header.volume.VolumeVO; import org.zstack.header.volume.VolumeVO_; import org.zstack.header.volume.block.BlockVolumeVO; @@ -1103,7 +1104,7 @@ public void iJoin(ManagementNodeInventory inv) { } @Override - public List allocatePrimaryStorage(Set requiredFeatures, List candidates) { + public List allocatePrimaryStorage(Set requiredFeatures, String requiredProtocol, List candidates) { if (requiredFeatures.contains(PrimaryStorageFeature.SHARED_VOLUME)) { List excludeCandidates = candidates.stream() .filter(v -> PrimaryStorageConstant.EXTERNAL_PRIMARY_STORAGE_TYPE.equals(v.getType())) @@ -1115,6 +1116,37 @@ public List allocatePrimaryStorage(Set candidates.removeAll(excludeCandidates); } + if (requiredFeatures.contains(PrimaryStorageFeature.LEGACY_BOOT)) { + List externalCandidates = candidates.stream() + .filter(v -> PrimaryStorageConstant.EXTERNAL_PRIMARY_STORAGE_TYPE.equals(v.getType())) + .filter(v -> controllers.containsKey(v.getUuid())) + .collect(Collectors.toList()); + + if (!externalCandidates.isEmpty()) { + Map protocolByUuid = requiredProtocol != null ? Collections.emptyMap() : + Q.New(ExternalPrimaryStorageVO.class) + .select(ExternalPrimaryStorageVO_.uuid, ExternalPrimaryStorageVO_.defaultProtocol) + .in(ExternalPrimaryStorageVO_.uuid, externalCandidates.stream().map(PrimaryStorageVO::getUuid).collect(Collectors.toList())) + .listTuple().stream() + .collect(Collectors.toMap(t -> t.get(0, String.class), t -> t.get(1, String.class))); + + List excludeCandidates = externalCandidates.stream() + .filter(v -> { + String protocol = requiredProtocol != null ? requiredProtocol : protocolByUuid.get(v.getUuid()); + // only vhost-user-blk exposes the raw logical sector to the guest; other protocols + // go through the qemu block layer which does 512e emulation + return VolumeProtocol.Vhost.toString().equals(protocol) + && controllers.get(v.getUuid()).reportCapabilities().getMinLogicalSectorSize() > 512; + }) + .collect(Collectors.toList()); + + logger.info(String.format("exclude external primary storage candidates: %s for legacy boot feature, " + + "they expose a logical sector size larger than 512 bytes on which a Legacy-BIOS image cannot boot", excludeCandidates)); + + candidates.removeAll(excludeCandidates); + } + } + return candidates; } diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageFeatureAllocatorExtensionPoint.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageFeatureAllocatorExtensionPoint.java index da53a4da48b..649800e30d1 100644 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageFeatureAllocatorExtensionPoint.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageFeatureAllocatorExtensionPoint.java @@ -11,5 +11,5 @@ * @ Date : Created in 10:49 2025/7/15 */ public interface PrimaryStorageFeatureAllocatorExtensionPoint { - List allocatePrimaryStorage(Set requiredFeatures, List candidates); + List allocatePrimaryStorage(Set requiredFeatures, String requiredProtocol, List candidates); } diff --git a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageFeatureAllocatorFlow.java b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageFeatureAllocatorFlow.java index 6abaf247855..4b6e949510e 100644 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageFeatureAllocatorFlow.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageFeatureAllocatorFlow.java @@ -33,7 +33,7 @@ public void run(FlowTrigger trigger, Map data) { List candidates = (List) data.get(PrimaryStorageConstant.AllocatorParams.CANDIDATES); List ret; for (PrimaryStorageFeatureAllocatorExtensionPoint extp : pluginRgty.getExtensionList(PrimaryStorageFeatureAllocatorExtensionPoint.class)) { - ret = extp.allocatePrimaryStorage(spec.getRequiredFeatures(), candidates); + ret = extp.allocatePrimaryStorage(spec.getRequiredFeatures(), spec.getRequiredProtocol(), candidates); if (ret == null) { continue; } 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 ca4e6ac05f6..e5a6a269118 100755 --- a/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java +++ b/storage/src/main/java/org/zstack/storage/primary/PrimaryStorageManagerImpl.java @@ -60,6 +60,7 @@ import org.zstack.header.vm.VmInstanceStartExtensionPoint; import org.zstack.header.vm.*; import org.zstack.resourceconfig.*; +import org.zstack.storage.volume.VolumeSystemTags; import org.zstack.tag.TagManager; import org.zstack.utils.*; import org.zstack.utils.function.Function; @@ -830,6 +831,7 @@ private PrimaryStorageAllocationSpec buildAllocateSpecFromMsg(AllocatePrimarySto PrimaryStorageAllocationSpec spec = new PrimaryStorageAllocationSpec(); spec.setPossiblePrimaryStorageTypes(msg.getPossiblePrimaryStorageTypes()); spec.setRequiredFeatures(msg.getRequiredFeatures()); + spec.setRequiredProtocol(resolveRequiredProtocol(msg)); spec.setExcludePrimaryStorageTypes(msg.getExcludePrimaryStorageTypes()); spec.setImageUuid(msg.getImageUuid()); spec.setDiskOfferingUuid(msg.getDiskOfferingUuid()); @@ -858,6 +860,14 @@ private PrimaryStorageAllocationSpec buildAllocateSpecFromMsg(AllocatePrimarySto return spec; } + private String resolveRequiredProtocol(AllocatePrimaryStorageMsg msg) { + String protocolTag = msg.getSystemTag(VolumeSystemTags.VOLUME_PROTOCOL::isMatch); + if (protocolTag == null) { + return null; + } + return VolumeSystemTags.VOLUME_PROTOCOL.getTokenByTag(protocolTag, VolumeSystemTags.VOLUME_PROTOCOL_TOKEN); + } + @Override public String getId() { return bus.makeLocalServiceId(PrimaryStorageConstant.SERVICE_ID); @@ -1027,7 +1037,7 @@ public void sort(List primaryStorageVOS, VmInstanceSpec.ImageS } @Override - public List allocatePrimaryStorage(Set requiredFeatures, List candidates) { + public List allocatePrimaryStorage(Set requiredFeatures, String requiredProtocol, List candidates) { if (requiredFeatures.contains(PrimaryStorageFeature.SHARED_VOLUME)) { candidates = candidates.stream() .filter(v -> PrimaryStorageType.getSupportFeaturesTypes(PrimaryStorageType::isSupportSharedVolume) diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsVhostLegacyBootAllocateCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsVhostLegacyBootAllocateCase.groovy new file mode 100644 index 00000000000..72c182ae7f6 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsVhostLegacyBootAllocateCase.groovy @@ -0,0 +1,210 @@ +package org.zstack.test.integration.storage.primary.addon.zbs + +import org.springframework.http.HttpEntity +import org.zstack.core.cloudbus.CloudBus +import org.zstack.header.image.ImageVO +import org.zstack.header.storage.backup.UploadImageToRemoteTargetMsg +import org.zstack.header.storage.backup.UploadImageToRemoteTargetReply +import org.zstack.sdk.ClusterInventory +import org.zstack.sdk.CreateVmInstanceAction +import org.zstack.sdk.ImageInventory +import org.zstack.sdk.InstanceOfferingInventory +import org.zstack.sdk.L3NetworkInventory +import org.zstack.sdk.PrimaryStorageInventory +import org.zstack.sdk.SystemTagInventory +import org.zstack.storage.zbs.ZbsConstants +import org.zstack.storage.zbs.ZbsStorageController +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.utils.gson.JSONObjectUtil + +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_STORAGE_PRIMARY_10035 + +class ZbsVhostLegacyBootAllocateCase extends SubCase { + EnvSpec env + CloudBus bus + PrimaryStorageInventory ps + ClusterInventory cluster + InstanceOfferingInventory instanceOffering + ImageInventory image + L3NetworkInventory l3 + + @Override + void clean() { + env.delete() + } + + @Override + void setup() { + useSpring(StorageTest.springSpec) + } + + @Override + void environment() { + env = makeEnv { + instanceOffering { + name = "instanceOffering" + memory = SizeUnit.GIGABYTE.toByte(8) + cpu = 4 + } + + sftpBackupStorage { + name = "sftp" + url = "/sftp" + username = "root" + password = "password" + hostname = "127.0.0.2" + + image { + name = "image" + url = "http://zstack.org/download/test.qcow2" + size = SizeUnit.GIGABYTE.toByte(1) + virtio = true + } + } + + zone { + name = "zone" + + cluster { + name = "cluster" + hypervisorType = "KVM" + + kvm { + name = "kvm-1" + managementIp = "127.0.0.1" + username = "root" + password = "password" + } + + attachL2Network("l2") + attachPrimaryStorage("zbs-vhost") + } + + l2NoVlanNetwork { + name = "l2" + physicalInterface = "eth0" + + l3Network { + name = "l3" + ip { + startIp = "192.168.100.10" + endIp = "192.168.100.100" + netmask = "255.255.255.0" + gateway = "192.168.100.1" + } + } + } + + externalPrimaryStorage { + name = "zbs-vhost" + identity = "zbs" + defaultOutputProtocol = "Vhost" + config = "{\"mdsUrls\":[\"root:password@127.0.1.1\",\"root:password@127.0.1.2\",\"root:password@127.0.1.3\"],\"logicalPoolName\":\"lpool1\"}" + url = "zbs" + } + + attachBackupStorage("sftp") + } + } + } + + @Override + void test() { + env.create { + ps = env.inventoryByName("zbs-vhost") as PrimaryStorageInventory + cluster = env.inventoryByName("cluster") as ClusterInventory + instanceOffering = env.inventoryByName("instanceOffering") as InstanceOfferingInventory + image = env.inventoryByName("image") as ImageInventory + l3 = env.inventoryByName("l3") as L3NetworkInventory + bus = bean(CloudBus.class) + + registerBaseStubs() + + testLegacyImageRejectedWhenOnlyVhostPsCandidate() + testUefiImageNotRejectedByLegacyBootFilter() + } + } + + void testLegacyImageRejectedWhenOnlyVhostPsCandidate() { + setImageBootMode(image.uuid, "Legacy") + + CreateVmInstanceAction action = new CreateVmInstanceAction() + action.name = "legacy-vm" + action.instanceOfferingUuid = instanceOffering.uuid + action.imageUuid = image.uuid + action.l3NetworkUuids = [l3.uuid] + action.primaryStorageUuidForRootVolume = ps.uuid + action.sessionId = adminSession() + + CreateVmInstanceAction.Result result = action.call() + assert result.error != null : "Legacy image with only a ZBS-vhost candidate PS must fail at allocate stage" + assert JSONObjectUtil.toJsonString(result.error).contains(ORG_ZSTACK_STORAGE_PRIMARY_10035) : + "Legacy vm should be rejected by the primary storage feature allocator, actual: ${JSONObjectUtil.toJsonString(result.error)}" + } + + void testUefiImageNotRejectedByLegacyBootFilter() { + setImageBootMode(image.uuid, "UEFI") + + CreateVmInstanceAction action = new CreateVmInstanceAction() + action.name = "uefi-vm" + action.instanceOfferingUuid = instanceOffering.uuid + action.imageUuid = image.uuid + action.l3NetworkUuids = [l3.uuid] + action.primaryStorageUuidForRootVolume = ps.uuid + action.sessionId = adminSession() + + CreateVmInstanceAction.Result result = action.call() + assert result.error == null || + !JSONObjectUtil.toJsonString(result.error).contains("returns zero primary storage candidate") : + "UEFI image must not be rejected by the legacy boot allocator filter" + } + + void setImageBootMode(String imageUuid, String bootMode) { + def existing = querySystemTag { + conditions = ["resourceUuid=${imageUuid}".toString(), "tag~=bootMode::%"] + } as List + + if (existing.isEmpty()) { + createSystemTag { + resourceUuid = imageUuid + resourceType = ImageVO.getSimpleName() + tag = "bootMode::${bootMode}".toString() + } + } else { + updateSystemTag { + uuid = existing.get(0).uuid + tag = "bootMode::${bootMode}".toString() + } + } + } + + void registerBaseStubs() { + env.message(UploadImageToRemoteTargetMsg.class) { UploadImageToRemoteTargetMsg msg, CloudBus b -> + b.reply(msg, new UploadImageToRemoteTargetReply()) + } + + env.simulator(ZbsStorageController.PREPARE_VHOST_TARGET_ENV_PATH) { HttpEntity e, EnvSpec spec -> + return new ZbsStorageController.AgentResponse() + } + env.simulator(ZbsStorageController.DEPLOY_VHOST_PATH) { HttpEntity e, EnvSpec spec -> + return new ZbsStorageController.AgentResponse() + } + env.simulator(ZbsStorageController.VHOST_TARGET_HEALTH_PATH) { HttpEntity e, EnvSpec spec -> + def rsp = new ZbsStorageController.VhostTargetHealthRsp() + rsp.targetRunning = true + return rsp + } + env.afterSimulator(ZbsStorageController.CREATE_VOLUME_PATH) { rsp, HttpEntity e -> + def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.CreateVolumeCmd) + if (cmd.volume == ZbsConstants.ZBS_HEARTBEAT_VOLUME_NAME) { + def vrsp = new ZbsStorageController.CreateVolumeRsp() + vrsp.installPath = "zbs://${cmd.logicalPool}/${cmd.volume}".toString() + return vrsp + } + return rsp + } + } +} diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsVhostVolumeCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsVhostVolumeCase.groovy index 941152f237b..eae5b9cf7b6 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsVhostVolumeCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsVhostVolumeCase.groovy @@ -422,6 +422,12 @@ class ZbsVhostVolumeCase extends SubCase { return rsp } + createSystemTag { + resourceUuid = image.uuid + resourceType = org.zstack.header.image.ImageVO.getSimpleName() + tag = "bootMode::UEFI" + } + VmInstanceInventory vm = createVmInstance { name = "vhost-vm" instanceOfferingUuid = instanceOffering.uuid