Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
import org.zstack.header.allocator.*;
import org.zstack.header.configuration.DiskOfferingInventory;
import org.zstack.header.configuration.DiskOfferingVO;
import org.zstack.header.storage.primary.PrimaryStorageConstants;
import org.zstack.header.storage.primary.PrimaryStorageVO;
import org.zstack.header.storage.primary.PrimaryStorageVO_;
import org.zstack.header.core.workflow.Flow;
import org.zstack.header.core.workflow.FlowRollback;
import org.zstack.header.core.workflow.FlowTrigger;
Expand Down Expand Up @@ -60,25 +63,39 @@ private long getTotalDataDiskSize(VmInstanceSpec spec) {
return size;
}

private long getDataDiskSizeOnLocalStorage(VmInstanceSpec spec) {
String dataPs = spec.getRequiredPrimaryStorageUuidForDataVolume();
if (dataPs == null) {
return getTotalDataDiskSize(spec);
}
boolean dataOnLocal = PrimaryStorageConstants.LOCAL_STORAGE_TYPE.equals(
org.zstack.core.db.Q.New(PrimaryStorageVO.class)
.eq(PrimaryStorageVO_.uuid, dataPs)
.select(PrimaryStorageVO_.type)
.findValue());
return dataOnLocal ? getTotalDataDiskSize(spec) : 0;
}

protected AllocateHostMsg prepareMsg(VmInstanceSpec spec) {
DesignatedAllocateHostMsg msg = new DesignatedAllocateHostMsg();

List<DiskOfferingInventory> 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);
long diskSize = rootDiskSize + getTotalDataDiskSize(spec);
diskOfferings.addAll(spec.getDataDiskOfferings());
msg.setSoftAvoidHostUuids(spec.getSoftAvoidHostUuids());
msg.setAvoidHostUuids(spec.getAvoidHostUuids());
msg.setDiskOfferings(diskOfferings);
msg.setDiskSize(diskSize);
msg.setLocalStorageDiskSize(rootDiskSize + getDataDiskSizeOnLocalStorage(spec));
Comment on lines +66 to +98

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

localStorageDiskSize 仍然漏掉了“按数据盘分别选主存储”的路径。

Line 67 这里只看单个 spec.getRequiredPrimaryStorageUuidForDataVolume(),但同一个 prepareMsg() 在 Line 139-154 已经支持多候选 data PS 以及 APICreateVmInstanceMsg.DiskAO.primaryStorageUuid。这样一来,只要一次创建里部分数据盘落在 local、部分落在 NFS,这里的本地容量还是会被按“全算/全不算”处理,继续把 mixed-storage 请求误判为 NO_AVAILABLE_HOST(或反向少算)。这里需要按每块数据盘的目标 PS 逐个累加本地容量,而不是依赖单个 data-volume PS 提示。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java` around
lines 66 - 98, In VmAllocateHostFlow.prepareMsg(), the localStorageDiskSize
calculation is still using getDataDiskSizeOnLocalStorage(spec), which only
checks spec.getRequiredPrimaryStorageUuidForDataVolume() and cannot handle
per-data-disk primary storage selection. Update the logic to inspect each data
disk’s target PrimaryStorageUuid individually (including the
APICreateVmInstanceMsg.DiskAO.primaryStorageUuid path already supported
elsewhere in this flow) and sum only the portions that are actually on
LOCAL_STORAGE_TYPE before setting msg.setLocalStorageDiskSize().

msg.setCpuCapacity(spec.getVmInventory().getCpuNum());
msg.setMemoryCapacity(spec.getVmInventory().getMemorySize());
msg.setClusterUuids(spec.getRequiredClusterUuids());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class AllocateHostMsg extends NeedReplyMessage {
private long cpuCapacity;
private long memoryCapacity;
private long diskSize;
private long localStorageDiskSize;
private String allocatorStrategy;
private List<String> avoidHostUuids;
private List<String> softAvoidHostUuids;
Expand Down Expand Up @@ -150,6 +151,14 @@ public void setDiskSize(long diskSize) {
this.diskSize = diskSize;
}

public long getLocalStorageDiskSize() {
return localStorageDiskSize;
}

public void setLocalStorageDiskSize(long localStorageDiskSize) {
this.localStorageDiskSize = localStorageDiskSize;
}

public String getAllocatorStrategy() {
return allocatorStrategy;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class HostAllocatorSpec {
private long memoryCapacity;
private List<String> l3NetworkUuids;
private long diskSize;
private long localStorageDiskSize;
private String hypervisorType;
private String allocatorStrategy;
private VmInstanceInventory vmInstance;
Expand Down Expand Up @@ -199,6 +200,14 @@ public void setDiskSize(long diskSize) {
this.diskSize = diskSize;
}

public long getLocalStorageDiskSize() {
return localStorageDiskSize > 0 ? localStorageDiskSize : diskSize;
}

public void setLocalStorageDiskSize(long localStorageDiskSize) {
this.localStorageDiskSize = localStorageDiskSize;
}

public String getHypervisorType() {
return hypervisorType;
}
Expand Down Expand Up @@ -262,6 +271,7 @@ public static HostAllocatorSpec fromAllocationMsg(AllocateHostMsg msg) {
spec.setSoftAvoidHostUuids(msg.getSoftAvoidHostUuids());
spec.setCpuCapacity(msg.getCpuCapacity());
spec.setDiskSize(msg.getDiskSize());
spec.setLocalStorageDiskSize(msg.getLocalStorageDiskSize());
spec.setListAllHosts(msg.isListAllHosts());
spec.setDryRun(msg.isDryRun());
spec.setFullAllocate(msg.isFullAllocate());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public List<HostVO> filterHostCandidates(List<HostVO> candidates, HostAllocatorS
}
List<LocalStorageHostRefVO> refs = q.list();

long requiredSize = spec.getLocalStorageDiskSize();
final Set<String> toRemoveHuuids = new HashSet<>();
final Set<String> toAddHuuids = new HashSet<>();
for (LocalStorageHostRefVO ref : refs) {
Expand All @@ -130,7 +131,7 @@ public List<HostVO> filterHostCandidates(List<HostVO> candidates, HostAllocatorS
// 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);
Expand All @@ -143,7 +144,7 @@ public List<HostVO> filterHostCandidates(List<HostVO> candidates, HostAllocatorS
toRemoveHuuids.removeAll(toAddHuuids);
if (!toRemoveHuuids.isEmpty()) {
logger.debug(String.format("local storage filters out hosts%s, because they don't have required disk capacity[%s bytes]",
toRemoveHuuids, spec.getDiskSize()));
toRemoveHuuids, requiredSize));

candidates = CollectionUtils.transformToList(candidates, new Function<HostVO, HostVO>() {
@Override
Expand All @@ -155,7 +156,7 @@ public HostVO call(HostVO arg) {
if (candidates.isEmpty()) {
throw new OperationFailureException(err(ORG_ZSTACK_STORAGE_PRIMARY_LOCAL_10020, HostAllocatorError.NO_AVAILABLE_HOST,
"the local primary storage has no hosts with enough disk capacity[%s bytes] required by the vm[uuid:%s]",
spec.getDiskSize(), spec.getVmInstance().getUuid()
requiredSize, spec.getVmInstance().getUuid()
));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package org.zstack.test.integration.storage.primary.local_nfs.allocator.host

import org.zstack.compute.vm.VmSystemTags
import org.zstack.sdk.*
import org.zstack.test.integration.storage.StorageTest
import org.zstack.testlib.EnvSpec
import org.zstack.testlib.SubCase
import org.zstack.utils.data.SizeUnit

/**
* Regression: mixed storage. Root volume on local PS (fits), data volumes on NFS.
* Local PS host capacity only fits root; LocalStorageAllocatorFactory must NOT
* sum root+data (200G) against local host ref, otherwise NO_AVAILABLE_HOST is
* wrongly raised. See "no hosts with enough disk capacity[214748364800 bytes]".
*/
class CreateVmDataDiskOnNfsLocalCapacityCase extends SubCase {
EnvSpec env

@Override
void setup() {
useSpring(StorageTest.springSpec)
}

@Override
void environment() {
env = env {
instanceOffering {
name = "instanceOffering"
memory = SizeUnit.GIGABYTE.toByte(1)
cpu = 1
}

diskOffering {
name = "rootDisk"
diskSize = SizeUnit.GIGABYTE.toByte(40)
}

diskOffering {
name = "dataDisk"
diskSize = SizeUnit.GIGABYTE.toByte(80)
}

sftpBackupStorage {
name = "sftp"
url = "/sftp"
username = "root"
password = "password"
hostname = "localhost"

image {
name = "image"
url = "http://zstack.org/download/test.qcow2"
format = "raw"
mediaType = "ISO"
size = 0
}
}

zone {
name = "zone"
description = "test"

cluster {
name = "cluster"
hypervisorType = "KVM"

kvm {
name = "kvm"
managementIp = "localhost"
username = "root"
password = "password"
totalCpu = 88
totalMem = SizeUnit.GIGABYTE.toByte(100)
}

attachPrimaryStorage("local")
attachPrimaryStorage("nfs")
attachL2Network("l2")
}

nfsPrimaryStorage {
name = "nfs"
url = "172.20.0.1:/nfs_root"
totalCapacity = SizeUnit.GIGABYTE.toByte(1000)
availableCapacity = SizeUnit.GIGABYTE.toByte(1000)
}

localPrimaryStorage {
name = "local"
url = "/local_ps"
totalCapacity = SizeUnit.GIGABYTE.toByte(50)
availableCapacity = SizeUnit.GIGABYTE.toByte(50)
}

l2NoVlanNetwork {
name = "l2"
physicalInterface = "eth0"

l3Network {
name = "l3"

ip {
startIp = "12.16.10.10"
endIp = "12.16.10.100"
netmask = "255.255.255.0"
gateway = "12.16.10.1"
}
}
}

attachBackupStorage("sftp")
}
}
}

@Override
void test() {
env.create {
testCreateVmRootLocalDataNfs()
}
}

@Override
void clean() {
env.delete()
}

void testCreateVmRootLocalDataNfs() {
InstanceOfferingInventory instanceOffering = env.inventoryByName("instanceOffering") as InstanceOfferingInventory
DiskOfferingInventory rootDisk = env.inventoryByName("rootDisk") as DiskOfferingInventory
DiskOfferingInventory dataDisk = env.inventoryByName("dataDisk") 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 a = new CreateVmInstanceAction(
name: "mixed",
instanceOfferingUuid: instanceOffering.uuid,
imageUuid: image.uuid,
l3NetworkUuids: [l3.uuid],
hostUuid: host.uuid,
rootDiskOfferingUuid: rootDisk.uuid,
dataDiskOfferingUuids: [dataDisk.uuid, dataDisk.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 r = a.call()
assert r.error == null:
"root(40G) on local(50G), data(160G) on nfs: local capacity check wrongly summed root+data " +
"and raised NO_AVAILABLE_HOST. error=${r.error}"
}
}