diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java index 8993b278e9b..73d389d5807 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java @@ -52,33 +52,23 @@ public class VmAllocateHostFlow implements Flow { @Autowired protected VmInstanceExtensionPointEmitter extEmitter; - private long getTotalDataDiskSize(VmInstanceSpec spec) { - long size = 0; - for (DiskOfferingInventory dinv : spec.getDataDiskOfferings()) { - size += dinv.getDiskSize(); - } - return size; - } - protected AllocateHostMsg prepareMsg(VmInstanceSpec spec) { DesignatedAllocateHostMsg msg = new DesignatedAllocateHostMsg(); List diskOfferings = new ArrayList<>(); ImageInventory image = spec.getImageSpec().getInventory(); - long diskSize; + long rootDiskSize; if (image == null || (image.getMediaType() != null && image.getMediaType().equals(ImageMediaType.ISO.toString()))) { DiskOfferingVO dvo = dbf.findByUuid(spec.getRootDiskOffering().getUuid(), DiskOfferingVO.class); - diskSize = dvo.getDiskSize(); + rootDiskSize = dvo.getDiskSize(); diskOfferings.add(DiskOfferingInventory.valueOf(dvo)); } else { - diskSize = image.getSize(); + rootDiskSize = image.getSize(); } - diskSize += getTotalDataDiskSize(spec); diskOfferings.addAll(spec.getDataDiskOfferings()); msg.setSoftAvoidHostUuids(spec.getSoftAvoidHostUuids()); msg.setAvoidHostUuids(spec.getAvoidHostUuids()); msg.setDiskOfferings(diskOfferings); - msg.setDiskSize(diskSize); msg.setCpuCapacity(spec.getVmInventory().getCpuNum()); msg.setMemoryCapacity(spec.getVmInventory().getMemorySize()); msg.setClusterUuids(spec.getRequiredClusterUuids()); @@ -136,6 +126,15 @@ public String call(L3NetworkInventory arg) { msg.getRequiredPrimaryStorageUuids().addAll(spec.getDiskAOs().stream() .map(APICreateVmInstanceMsg.DiskAO::getPrimaryStorageUuid).filter(Objects::nonNull).collect(Collectors.toList())); } + String rootPsUuid = spec.getCandidatePrimaryStorageUuidsForRootVolume().size() == 1 ? + spec.getCandidatePrimaryStorageUuidsForRootVolume().get(0) : null; + msg.addRequiredDiskCapacity(rootPsUuid, rootDiskSize); + + String dataPsUuid = spec.getCandidatePrimaryStorageUuidsForDataVolume().size() == 1 ? + spec.getCandidatePrimaryStorageUuidsForDataVolume().get(0) : null; + for (DiskOfferingInventory dinv : spec.getDataDiskOfferings()) { + msg.addRequiredDiskCapacity(dataPsUuid, dinv.getDiskSize()); + } return msg; } diff --git a/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java b/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java index 132637b8635..b399a3d736e 100755 --- a/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java +++ b/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java @@ -10,7 +10,6 @@ public class AllocateHostMsg extends NeedReplyMessage { private long cpuCapacity; private long memoryCapacity; - private long diskSize; private String allocatorStrategy; private List avoidHostUuids; private List softAvoidHostUuids; @@ -27,6 +26,7 @@ public class AllocateHostMsg extends NeedReplyMessage { private Set requiredPrimaryStorageUuids = new HashSet<>(); // for each set in the list, the primary storage inside is optional private final List> optionalPrimaryStorageUuids = new ArrayList<>(); + private final List requiredDiskCapacities = new ArrayList<>(); private boolean fullAllocate = true; private long oldMemoryCapacity = 0; private AllocationScene allocationScene; @@ -70,6 +70,14 @@ public void addRequiredPrimaryStorageUuid(String requiredPrimaryStorageUuid) { this.requiredPrimaryStorageUuids.add(requiredPrimaryStorageUuid); } + public List getRequiredDiskCapacities() { + return requiredDiskCapacities; + } + + public void addRequiredDiskCapacity(String primaryStorageUuid, long size) { + requiredDiskCapacities.add(new RequiredDiskCapacity(primaryStorageUuid, size)); + } + public String getRequiredBackupStorageUuid() { return requiredBackupStorageUuid; } @@ -143,11 +151,13 @@ public void setMemoryCapacity(long memoryCapacity) { } public long getDiskSize() { - return diskSize; + return requiredDiskCapacities.stream().mapToLong(RequiredDiskCapacity::getSize).sum(); } + // Compatibility entry for callers that cannot determine primary storage yet. public void setDiskSize(long diskSize) { - this.diskSize = diskSize; + requiredDiskCapacities.clear(); + requiredDiskCapacities.add(new RequiredDiskCapacity(null, diskSize)); } public String getAllocatorStrategy() { diff --git a/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java b/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java index 33d39896c82..f3d1990a992 100755 --- a/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java +++ b/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java @@ -14,7 +14,6 @@ public class HostAllocatorSpec { private long cpuCapacity; private long memoryCapacity; private List l3NetworkUuids; - private long diskSize; private String hypervisorType; private String allocatorStrategy; private VmInstanceInventory vmInstance; @@ -29,6 +28,7 @@ public class HostAllocatorSpec { private Set requiredPrimaryStorageUuids = new HashSet<>(); // for each set in the list, the primary storage inside is optional private final List> optionalPrimaryStorageUuids = new ArrayList<>(); + private final List requiredDiskCapacities = new ArrayList<>(); private Map> backupStoragePrimaryStorageMetrics; private boolean dryRun; private List systemTags; @@ -89,6 +89,17 @@ public Set getRequiredPrimaryStorageUuids() { return requiredPrimaryStorageUuids; } + public List getRequiredDiskCapacities() { + return requiredDiskCapacities; + } + + public void setRequiredDiskCapacities(List requiredDiskCapacities) { + this.requiredDiskCapacities.clear(); + if (requiredDiskCapacities != null) { + this.requiredDiskCapacities.addAll(requiredDiskCapacities); + } + } + public List> getOptionalPrimaryStorageUuids() { return optionalPrimaryStorageUuids; } @@ -192,11 +203,7 @@ public void setL3NetworkUuids(List l3NetworkUuids) { } public long getDiskSize() { - return diskSize; - } - - public void setDiskSize(long diskSize) { - this.diskSize = diskSize; + return requiredDiskCapacities.stream().mapToLong(RequiredDiskCapacity::getSize).sum(); } public String getHypervisorType() { @@ -261,7 +268,6 @@ public static HostAllocatorSpec fromAllocationMsg(AllocateHostMsg msg) { spec.setAvoidHostUuids(msg.getAvoidHostUuids()); spec.setSoftAvoidHostUuids(msg.getSoftAvoidHostUuids()); spec.setCpuCapacity(msg.getCpuCapacity()); - spec.setDiskSize(msg.getDiskSize()); spec.setListAllHosts(msg.isListAllHosts()); spec.setDryRun(msg.isDryRun()); spec.setFullAllocate(msg.isFullAllocate()); @@ -280,6 +286,7 @@ public static HostAllocatorSpec fromAllocationMsg(AllocateHostMsg msg) { spec.setAllowNoL3Networks(msg.isAllowNoL3Networks()); spec.setRequiredBackupStorageUuid(msg.getRequiredBackupStorageUuid()); spec.setRequiredPrimaryStorageUuids(msg.getRequiredPrimaryStorageUuids()); + spec.setRequiredDiskCapacities(msg.getRequiredDiskCapacities()); msg.getOptionalPrimaryStorageUuids().forEach(spec::addOptionalPrimaryStorageUuids); spec.setAllocationScene(msg.getAllocationScene()); spec.setArchitecture(msg.getArchitecture()); diff --git a/header/src/main/java/org/zstack/header/allocator/RequiredDiskCapacity.java b/header/src/main/java/org/zstack/header/allocator/RequiredDiskCapacity.java new file mode 100644 index 00000000000..63a1ffd94d0 --- /dev/null +++ b/header/src/main/java/org/zstack/header/allocator/RequiredDiskCapacity.java @@ -0,0 +1,30 @@ +package org.zstack.header.allocator; + +public class RequiredDiskCapacity { + private String primaryStorageUuid; + private long size; + + public RequiredDiskCapacity() { + } + + public RequiredDiskCapacity(String primaryStorageUuid, long size) { + this.primaryStorageUuid = primaryStorageUuid; + this.size = size; + } + + public String getPrimaryStorageUuid() { + return primaryStorageUuid; + } + + public void setPrimaryStorageUuid(String primaryStorageUuid) { + this.primaryStorageUuid = primaryStorageUuid; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } +} diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java index 0c90fd3d9bb..a834b4a9db3 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java @@ -15,6 +15,7 @@ import org.zstack.header.allocator.HostAllocatorFilterExtensionPoint; import org.zstack.header.allocator.HostAllocatorSpec; import org.zstack.header.allocator.HostAllocatorStrategyExtensionPoint; +import org.zstack.header.allocator.RequiredDiskCapacity; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.host.HostInventory; import org.zstack.header.host.HostVO; @@ -109,13 +110,13 @@ public List filterHostCandidates(List candidates, HostAllocatorS long reservedCapacity = SizeUtils.sizeStringToBytes(PrimaryStorageGlobalConfig.RESERVED_CAPACITY.value()); if (VmOperation.NewCreate.toString().equals(spec.getVmOperation()) || VmOperation.MigrateVolume.toString().equals(spec.getVmOperation())) { - List huuids = getNeedCheckHostLocalStorageList(candidates, spec); - if (huuids.isEmpty()) { + Map needCheckLocalStorageHosts = getNeedCheckLocalStorageHosts(candidates, spec); + if (needCheckLocalStorageHosts.isEmpty()) { return candidates; } SimpleQuery q = dbf.createQuery(LocalStorageHostRefVO.class); - q.add(LocalStorageHostRefVO_.hostUuid, Op.IN, huuids); + q.add(LocalStorageHostRefVO_.hostUuid, Op.IN, needCheckLocalStorageHosts.keySet()); if (!spec.getRequiredPrimaryStorageUuids().isEmpty()) { q.add(LocalStorageHostRefVO_.primaryStorageUuid, Op.IN, spec.getRequiredPrimaryStorageUuids()); } @@ -126,10 +127,20 @@ public List filterHostCandidates(List candidates, HostAllocatorS for (LocalStorageHostRefVO ref : refs) { String huuid = ref.getHostUuid(); String psUuid = ref.getPrimaryStorageUuid(); + boolean onlyLocalStorage = needCheckLocalStorageHosts.get(huuid); + long requiredSize = spec.getRequiredDiskCapacities().stream() + .filter(it -> psUuid.equals(it.getPrimaryStorageUuid()) || + (onlyLocalStorage && it.getPrimaryStorageUuid() == null)) + .mapToLong(RequiredDiskCapacity::getSize) + .sum(); + if (requiredSize == 0) { + continue; + } + // check primary storage capacity and host physical capacity boolean capacityChecked = PrimaryStorageCapacityChecker.New(psUuid, ref.getAvailableCapacity(), ref.getTotalPhysicalCapacity(), ref.getAvailablePhysicalCapacity()) - .checkRequiredSize(spec.getDiskSize()); + .checkRequiredSize(requiredSize); if (!capacityChecked) { addHostPrimaryStorageBlacklist(huuid, psUuid, spec); @@ -212,7 +223,7 @@ private void checkLocalStorageForVmStart(VmInstanceInventory vm, List ca } /** - * @return hostUuid list + * @return hostUuid and whether the host is attached to local storage only *

* Just check it : * The current cluster is mounted only local storage @@ -221,7 +232,7 @@ private void checkLocalStorageForVmStart(VmInstanceInventory vm, List ca * Negative impact * In the case of local + non-local and no ps specified (non-local is Disconnected/Disabled, or non-local capacity not enough), the allocated host may not have enough disks */ - private List getNeedCheckHostLocalStorageList(List candidates, HostAllocatorSpec spec) { + private Map getNeedCheckLocalStorageHosts(List candidates, HostAllocatorSpec spec) { boolean isRequireNonLocalStorage = spec.getRequiredPrimaryStorageUuids() .stream().noneMatch(LocalStorageUtils::isLocalStorage); Map> grouped = candidates.stream().collect( @@ -231,14 +242,16 @@ private List getNeedCheckHostLocalStorageList(List candidates, H ) ); - List result = new ArrayList<>(); + Map result = new HashMap<>(); for (Map.Entry> entry : grouped.entrySet()) { boolean isOnlyAttachedLocalStorage = LocalStorageUtils.isOnlyAttachedLocalStorage(entry.getKey()); if (!isOnlyAttachedLocalStorage && (isRequireNonLocalStorage || spec.isDryRun())) { continue; } - result.addAll(entry.getValue()); + for (String hostUuid : entry.getValue()) { + result.put(hostUuid, isOnlyAttachedLocalStorage); + } } return result; diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/local_nfs/allocator/host/CreateVmHostAllocateCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/local_nfs/allocator/host/CreateVmHostAllocateCase.groovy index 36709557942..af3941807fa 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/local_nfs/allocator/host/CreateVmHostAllocateCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/local_nfs/allocator/host/CreateVmHostAllocateCase.groovy @@ -116,6 +116,8 @@ class CreateVmHostAllocateCase extends SubCase { env.create { testGetCandidateZonesClustersHostsForCreatingVm() + testCreateVmAssignLocalAndNfs() + testCreateVmAssignNfs() } } @@ -142,6 +144,60 @@ class CreateVmHostAllocateCase extends SubCase { assert 2 == hosts.size() } + void testCreateVmAssignLocalAndNfs() { + InstanceOfferingInventory instanceOffering = env.inventoryByName("instanceOffering") as InstanceOfferingInventory + DiskOfferingInventory diskOffering = env.inventoryByName("diskOffering") as DiskOfferingInventory + ImageInventory image = env.inventoryByName("image") as ImageInventory + L3NetworkInventory l3 = env.inventoryByName("l3") as L3NetworkInventory + HostInventory host = env.inventoryByName("kvm") + PrimaryStorageInventory nfs = env.inventoryByName("nfs") + PrimaryStorageInventory local = env.inventoryByName("local") + + CreateVmInstanceAction rootAndDataLocalAction = new CreateVmInstanceAction( + name : "rootAndDataLocalVm", + instanceOfferingUuid : instanceOffering.uuid, + imageUuid : image.uuid, + l3NetworkUuids : [l3.uuid], + hostUuid : host.uuid, + dataDiskOfferingUuids : [diskOffering.uuid], + primaryStorageUuidForRootVolume : local.uuid, + systemTags : [VmSystemTags.PRIMARY_STORAGE_UUID_FOR_DATA_VOLUME.instantiateTag([(VmSystemTags.PRIMARY_STORAGE_UUID_FOR_DATA_VOLUME_TOKEN): local.uuid])], + sessionId : currentEnvSpec.session.uuid + ) + assert null != rootAndDataLocalAction.call().error + + CreateVmInstanceAction rootLocalDataNfsAction = new CreateVmInstanceAction( + name : "rootLocalDataNfsVm", + instanceOfferingUuid : instanceOffering.uuid, + imageUuid : image.uuid, + l3NetworkUuids : [l3.uuid], + hostUuid : host.uuid, + dataDiskOfferingUuids : [diskOffering.uuid], + primaryStorageUuidForRootVolume : local.uuid, + systemTags : [VmSystemTags.PRIMARY_STORAGE_UUID_FOR_DATA_VOLUME.instantiateTag([(VmSystemTags.PRIMARY_STORAGE_UUID_FOR_DATA_VOLUME_TOKEN): nfs.uuid])], + sessionId : currentEnvSpec.session.uuid + ) + CreateVmInstanceAction.Result rootLocalDataNfsResult = rootLocalDataNfsAction.call() + assert null == rootLocalDataNfsResult.error + checkVmRootDiskPs(rootLocalDataNfsResult.value.inventory, local.uuid) + checkVmDataDiskPs(rootLocalDataNfsResult.value.inventory, nfs.uuid) + + CreateVmInstanceAction rootLocalDataUnspecifiedAction = new CreateVmInstanceAction( + name : "rootLocalDataUnspecifiedVm", + instanceOfferingUuid : instanceOffering.uuid, + imageUuid : image.uuid, + l3NetworkUuids : [l3.uuid], + hostUuid : host.uuid, + dataDiskOfferingUuids : [diskOffering.uuid], + primaryStorageUuidForRootVolume : local.uuid, + sessionId : currentEnvSpec.session.uuid + ) + CreateVmInstanceAction.Result rootLocalDataUnspecifiedResult = rootLocalDataUnspecifiedAction.call() + assert null == rootLocalDataUnspecifiedResult.error + checkVmRootDiskPs(rootLocalDataUnspecifiedResult.value.inventory, local.uuid) + checkVmDataDiskPs(rootLocalDataUnspecifiedResult.value.inventory, nfs.uuid) + } + void testCreateVmAssignNfs(){ InstanceOfferingInventory instanceOffering = env.inventoryByName("instanceOffering") as InstanceOfferingInventory DiskOfferingInventory diskOffering = env.inventoryByName("diskOffering") as DiskOfferingInventory @@ -184,4 +240,19 @@ class CreateVmHostAllocateCase extends SubCase { ) assert null != createVmInstanceAction.call().error } + + void checkVmRootDiskPs(VmInstanceInventory vm, String psUuid) { + VolumeInventory rootDisk = vm.allVolumes.find { it.uuid == vm.rootVolumeUuid } + assert rootDisk != null + assert psUuid == rootDisk.primaryStorageUuid + } + + void checkVmDataDiskPs(VmInstanceInventory vm, String psUuid) { + assert vm.allVolumes.size() > 1 + for (VolumeInventory disk : vm.allVolumes) { + if (disk.uuid != vm.rootVolumeUuid) { + assert psUuid == disk.primaryStorageUuid + } + } + } }