Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions build/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,11 @@
<artifactId>ovn</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.zstack</groupId>
<artifactId>zns</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.zstack</groupId>
<artifactId>observabilityServer</artifactId>
Expand Down
229 changes: 146 additions & 83 deletions compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SQLBatch;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.core.Completion;
import org.zstack.header.core.WhileDoneCompletion;
import org.zstack.header.core.workflow.Flow;
import org.zstack.header.core.workflow.FlowRollback;
Expand Down Expand Up @@ -97,14 +98,7 @@ public void run(final FlowTrigger trigger, final Map data) {
int deviceId = deviceIdBitmap.nextClearBit(0);
deviceIdBitmap.set(deviceId);
MacOperator mo = new MacOperator();
String customMac = mo.getMac(spec.getVmInventory().getUuid(), nw.getUuid());
if (customMac != null){
mo.deleteCustomMacSystemTag(spec.getVmInventory().getUuid(), nw.getUuid(), customMac);
customMac = customMac.toLowerCase();
} else {
customMac = MacOperator.generateMacWithDeviceId((short) deviceId);
}
final String mac = customMac;
final String mac = allocateMac(mo, spec, nw, deviceId);
CustomNicOperator nicOperator = new CustomNicOperator(spec.getVmInventory().getUuid(),nw.getUuid());
final String customNicUuid = nicOperator.getCustomNicId();

Expand All @@ -113,91 +107,42 @@ public void run(final FlowTrigger trigger, final Map data) {
if (type == null) {
errs.add(Platform.operr(ORG_ZSTACK_COMPUTE_VM_10068, "there is no available nicType on L3 network [%s]", nw.getUuid()));
wcomp.allDone();
return;
}
VmInstanceNicFactory vnicFactory = vmMgr.getVmInstanceNicFactory(type);


VmNicInventory nic = new VmNicInventory();
if (customNicUuid != null) {
nic.setUuid(customNicUuid);
} else {
nic.setUuid(Platform.getUuid());
}
/* the first ip is ipv4 address for dual stack nic */
nic.setVmInstanceUuid(spec.getVmInventory().getUuid());
nic.setL3NetworkUuid(nw.getUuid());
nic.setMac(mac);
nic.setHypervisorType(spec.getDestHost() == null ?
spec.getVmInventory().getHypervisorType() : spec.getDestHost().getHypervisorType());
VmNicInventory nic = buildNicInventory(spec, nicSpec, nw, mac, customNicUuid, deviceId, disableL3Networks);
if (mo.checkDuplicateMac(nic.getHypervisorType(), nic.getL3NetworkUuid(), nic.getMac())) {
trigger.fail(operr(ORG_ZSTACK_COMPUTE_VM_10069, "Duplicate mac address [%s]", nic.getMac()));
errs.add(operr(ORG_ZSTACK_COMPUTE_VM_10069, "Duplicate mac address [%s]", nic.getMac()));
wcomp.allDone();
return;
}

if (!StringUtils.isEmpty(nicSpec.getNicDriverType())) {
nic.setDriverType(nicSpec.getNicDriverType());
} else {
boolean vmImageHasVirtio = VmSystemTags.VIRTIO.hasTag(spec.getVmInventory().getUuid());
nicManager.setNicDriverType(nic, vmImageHasVirtio,
ImagePlatform.valueOf(spec.getVmInventory().getPlatform()).isParaVirtualization(),
spec.getVmInventory());
}
// Persist VmNicVO first so that ResourceVO entry exists before extensions
// (e.g. SDN controllers) attempt to create SystemTags referencing the NIC UUID.
VmNicVO nicVO = vnicFactory.createVmNic(nic, spec);

nic.setDeviceId(deviceId);
nic.setInternalName(VmNicVO.generateNicInternalName(spec.getVmInventory().getInternalId(), nic.getDeviceId()));
nic.setState(disableL3Networks.contains(nic.getL3NetworkUuid()) ? VmNicState.disable.toString() : VmNicState.enable.toString());
new SQLBatch() {
callBeforeAllocateVmNicExtensions(nic, spec, new Completion(wcomp) {
@Override
protected void scripts() {
VmNicVO nicVO = vnicFactory.createVmNic(nic, spec);
if (!nw.enableIpAddressAllocation() && nicNetworkInfoMap != null
&& nicNetworkInfoMap.containsKey(nw.getUuid())
&& spec.getVmInventory().getType().equals(VmInstanceConstant.USER_VM_TYPE)) {
NicIpAddressInfo nicNicIpAddressInfo = nicNetworkInfoMap.get(nic.getL3NetworkUuid());
if (!nicNicIpAddressInfo.ipv6Address.isEmpty()) {
UsedIpVO vo = new UsedIpVO();
vo.setUuid(Platform.getUuid());
vo.setIp(IPv6NetworkUtils.getIpv6AddressCanonicalString(nicNicIpAddressInfo.ipv6Address));
vo.setNetmask(IPv6NetworkUtils.getFormalNetmaskOfNetworkCidr(nicNicIpAddressInfo.ipv6Address+"/"+ nicNicIpAddressInfo.ipv6Prefix));
vo.setGateway(nicNicIpAddressInfo.ipv6Gateway.isEmpty() ? "" : IPv6NetworkUtils.getIpv6AddressCanonicalString(nicNicIpAddressInfo.ipv6Gateway));
vo.setIpVersion(IPv6Constants.IPv6);
vo.setVmNicUuid(nic.getUuid());
vo.setL3NetworkUuid(nic.getL3NetworkUuid());
vo.setIpInBinary(NetworkUtils.ipStringToBytes(vo.getIp()));
vo.setIpRangeUuid(new StaticIpOperator().getIpRangeUuid(nic.getL3NetworkUuid(), vo.getIp()));
nic.setUsedIpUuid(vo.getUuid());
nicVO.setUsedIpUuid(vo.getUuid());
nicVO.setIp(vo.getIp());
nicVO.setNetmask(vo.getNetmask());
nicVO.setGateway(vo.getGateway());
dbf.persist(vo);
public void success() {
new SQLBatch() {
@Override
protected void scripts() {
persistStaticIpIfNeeded(nic, nicVO, nw, nicNetworkInfoMap, spec);
nics.add(nic);
VmNicVO updated = dbf.updateAndRefresh(nicVO);
addVmNicConfig(updated, spec, nicSpec);
}
if (!nicNicIpAddressInfo.ipv4Address.isEmpty()) {
UsedIpVO vo = new UsedIpVO();
vo.setUuid(Platform.getUuid());
vo.setIp(nicNicIpAddressInfo.ipv4Address);
vo.setGateway(nicNicIpAddressInfo.ipv4Gateway);
vo.setNetmask(nicNicIpAddressInfo.ipv4Netmask);
vo.setIpVersion(IPv6Constants.IPv4);
vo.setVmNicUuid(nic.getUuid());
vo.setL3NetworkUuid(nic.getL3NetworkUuid());
vo.setIpInLong(NetworkUtils.ipv4StringToLong(vo.getIp()));
vo.setIpInBinary(NetworkUtils.ipStringToBytes(vo.getIp()));
vo.setIpRangeUuid(new StaticIpOperator().getIpRangeUuid(nic.getL3NetworkUuid(), vo.getIp()));
nic.setUsedIpUuid(vo.getUuid());
nicVO.setUsedIpUuid(vo.getUuid());
nicVO.setIp(vo.getIp());
nicVO.setNetmask(vo.getNetmask());
nicVO.setGateway(vo.getGateway());
dbf.persist(vo);
}
}
nics.add(nic);
nicVO = dbf.updateAndRefresh(nicVO);
addVmNicConfig(nicVO, spec, nicSpec);
}.execute();
wcomp.done();
}
}.execute();
wcomp.done();

@Override
public void fail(ErrorCode errorCode) {
errs.add(errorCode);
wcomp.allDone();
}
});
Comment on lines +121 to +145
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

在扩展失败时清理已落库的 VmNicVO

Line 123 已经持久化了 VmNicVO,但只有 Line 132 成功分支才把这张 NIC 放进后续集合;Line 141-144 失败时只是记错并结束。当前回滚逻辑会遍历 spec.getDestNics(),它看不到这张尚未加入集合的 NIC,因此这里会遗留孤儿 VmNicVO/ResourceVO

🛠️ 一个直接的修复方向
             callBeforeAllocateVmNicExtensions(nic, spec, new Completion(wcomp) {
                 `@Override`
                 public void success() {
                     new SQLBatch() {
@@
                 `@Override`
                 public void fail(ErrorCode errorCode) {
+                    vnicFactory.releaseVmNic(nic);
                     errs.add(errorCode);
                     wcomp.allDone();
                 }
             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java` around
lines 121 - 145, A VmNicVO is persisted by vnicFactory.createVmNic(nic, spec)
before callBeforeAllocateVmNicExtensions(...) but on extension failure the code
only records the error and doesn't remove that persisted nic, leaving an
orphaned VmNicVO/ResourceVO; in the Completion.fail(ErrorCode) branch delete the
persisted nicVO (and associated ResourceVO/any created system tags) inside a DB
transaction/SQLBatch (use dbf.remove(...) or equivalent) before adding the error
and calling wcomp.allDone(), ensuring the cleanup references the nicVO created
earlier so the rollback won't leave orphaned records.


}).run(new WhileDoneCompletion(trigger) {
@Override
Expand All @@ -211,6 +156,92 @@ public void done(ErrorCodeList errorCodeList) {
});
}

private String allocateMac(MacOperator mo, VmInstanceSpec spec, L3NetworkInventory nw, int deviceId) {
String vmUuid = spec.getVmInventory().getUuid();
String customMac = mo.getMac(vmUuid, nw.getUuid());
if (customMac != null) {
mo.deleteCustomMacSystemTag(vmUuid, nw.getUuid(), customMac);
return customMac.toLowerCase();
}
return MacOperator.generateMacWithDeviceId((short) deviceId);
}

private VmNicInventory buildNicInventory(VmInstanceSpec spec, VmNicSpec nicSpec,
L3NetworkInventory nw, String mac, String customNicUuid,
int deviceId, List<String> disableL3Networks) {
VmNicInventory nic = new VmNicInventory();
nic.setUuid(customNicUuid != null ? customNicUuid : Platform.getUuid());
/* the first ip is ipv4 address for dual stack nic */
nic.setVmInstanceUuid(spec.getVmInventory().getUuid());
nic.setL3NetworkUuid(nw.getUuid());
nic.setMac(mac);
nic.setHypervisorType(spec.getDestHost() == null ?
spec.getVmInventory().getHypervisorType() : spec.getDestHost().getHypervisorType());

if (!StringUtils.isEmpty(nicSpec.getNicDriverType())) {
nic.setDriverType(nicSpec.getNicDriverType());
} else {
boolean vmImageHasVirtio = VmSystemTags.VIRTIO.hasTag(spec.getVmInventory().getUuid());
nicManager.setNicDriverType(nic, vmImageHasVirtio,
ImagePlatform.valueOf(spec.getVmInventory().getPlatform()).isParaVirtualization(),
spec.getVmInventory());
}

nic.setDeviceId(deviceId);
nic.setInternalName(VmNicVO.generateNicInternalName(spec.getVmInventory().getInternalId(), nic.getDeviceId()));
nic.setState(disableL3Networks.contains(nic.getL3NetworkUuid()) ? VmNicState.disable.toString() : VmNicState.enable.toString());
return nic;
}

private void persistStaticIpIfNeeded(VmNicInventory nic, VmNicVO nicVO,
L3NetworkInventory nw, Map<String, NicIpAddressInfo> nicNetworkInfoMap,
VmInstanceSpec spec) {
if (nw.enableIpAddressAllocation() || nicNetworkInfoMap == null
|| !nicNetworkInfoMap.containsKey(nw.getUuid())
|| !spec.getVmInventory().getType().equals(VmInstanceConstant.USER_VM_TYPE)) {
return;
}

NicIpAddressInfo nicIpAddressInfo = nicNetworkInfoMap.get(nic.getL3NetworkUuid());
if (!nicIpAddressInfo.ipv6Address.isEmpty()) {
UsedIpVO vo = new UsedIpVO();
vo.setUuid(Platform.getUuid());
vo.setIp(IPv6NetworkUtils.getIpv6AddressCanonicalString(nicIpAddressInfo.ipv6Address));
vo.setNetmask(IPv6NetworkUtils.getFormalNetmaskOfNetworkCidr(nicIpAddressInfo.ipv6Address + "/" + nicIpAddressInfo.ipv6Prefix));
vo.setGateway(nicIpAddressInfo.ipv6Gateway.isEmpty() ? "" : IPv6NetworkUtils.getIpv6AddressCanonicalString(nicIpAddressInfo.ipv6Gateway));
vo.setIpVersion(IPv6Constants.IPv6);
vo.setVmNicUuid(nic.getUuid());
vo.setL3NetworkUuid(nic.getL3NetworkUuid());
vo.setIpInBinary(NetworkUtils.ipStringToBytes(vo.getIp()));
vo.setIpRangeUuid(new StaticIpOperator().getIpRangeUuid(nic.getL3NetworkUuid(), vo.getIp()));
nic.setUsedIpUuid(vo.getUuid());
nicVO.setUsedIpUuid(vo.getUuid());
nicVO.setIp(vo.getIp());
nicVO.setNetmask(vo.getNetmask());
nicVO.setGateway(vo.getGateway());
dbf.persist(vo);
}
if (!nicIpAddressInfo.ipv4Address.isEmpty()) {
UsedIpVO vo = new UsedIpVO();
vo.setUuid(Platform.getUuid());
vo.setIp(nicIpAddressInfo.ipv4Address);
vo.setGateway(nicIpAddressInfo.ipv4Gateway);
vo.setNetmask(nicIpAddressInfo.ipv4Netmask);
vo.setIpVersion(IPv6Constants.IPv4);
vo.setVmNicUuid(nic.getUuid());
vo.setL3NetworkUuid(nic.getL3NetworkUuid());
vo.setIpInLong(NetworkUtils.ipv4StringToLong(vo.getIp()));
vo.setIpInBinary(NetworkUtils.ipStringToBytes(vo.getIp()));
vo.setIpRangeUuid(new StaticIpOperator().getIpRangeUuid(nic.getL3NetworkUuid(), vo.getIp()));
nic.setUsedIpUuid(vo.getUuid());
nicVO.setUsedIpUuid(vo.getUuid());
nicVO.setIp(vo.getIp());
nicVO.setNetmask(vo.getNetmask());
nicVO.setGateway(vo.getGateway());
dbf.persist(vo);
}
}

private void addVmNicConfig(VmNicVO vmNicVO, VmInstanceSpec vmSpec, VmNicSpec nicSpec) {
if (nicSpec == null) {
return;
Expand All @@ -237,6 +268,38 @@ private void addVmNicConfig(VmNicVO vmNicVO, VmInstanceSpec vmSpec, VmNicSpec ni
}
}

private void callBeforeAllocateVmNicExtensions(VmNicInventory nic, VmInstanceSpec spec, Completion completion) {
List<BeforeAllocateVmNicExtensionPoint> exts = pluginRgty.getExtensionList(BeforeAllocateVmNicExtensionPoint.class);
if (exts.isEmpty()) {
completion.success();
return;
}

new While<>(exts).each((ext, wcomp) -> {
ext.beforeAllocateVmNic(nic, spec, new Completion(wcomp) {
@Override
public void success() {
wcomp.done();
}

@Override
public void fail(ErrorCode errorCode) {
wcomp.addError(errorCode);
wcomp.allDone();
}
});
}).run(new WhileDoneCompletion(completion) {
@Override
public void done(ErrorCodeList errorCodeList) {
if (errorCodeList.getCauses().isEmpty()) {
completion.success();
} else {
completion.fail(errorCodeList.getCauses().get(0));
}
}
});
}

@Override
public void rollback(final FlowRollback chain, Map data) {
final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString());
Expand All @@ -256,7 +319,7 @@ public void rollback(final FlowRollback chain, Map data) {
vnicFactory.releaseVmNic(vmNic);
}
dbf.removeByPrimaryKeys(destNics.stream().map(VmNicInventory::getUuid).collect(Collectors.toList()), VmNicVO.class);

chain.rollback();
return;
}
}
Loading