diff --git a/build/pom.xml b/build/pom.xml index a1ea6a47ca7..5a097bb382a 100755 --- a/build/pom.xml +++ b/build/pom.xml @@ -636,6 +636,11 @@ ovn ${project.version} + + org.zstack + zns + ${project.version} + org.zstack observabilityServer diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java index db5da3e3ce8..b572bf0ea1e 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateNicFlow.java @@ -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; @@ -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(); @@ -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(); + } + }); }).run(new WhileDoneCompletion(trigger) { @Override @@ -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 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 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; @@ -237,6 +268,38 @@ private void addVmNicConfig(VmNicVO vmNicVO, VmInstanceSpec vmSpec, VmNicSpec ni } } + private void callBeforeAllocateVmNicExtensions(VmNicInventory nic, VmInstanceSpec spec, Completion completion) { + List 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()); @@ -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; } } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocateSdnNicFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocateSdnNicFlow.java new file mode 100644 index 00000000000..1279f91245a --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateSdnNicFlow.java @@ -0,0 +1,122 @@ +package org.zstack.compute.vm; + +import org.springframework.beans.factory.annotation.Autowire; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.zstack.core.asyncbatch.While; +import org.zstack.core.componentloader.PluginRegistry; +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; +import org.zstack.header.core.workflow.FlowTrigger; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.vm.*; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.List; +import java.util.Map; + +import static org.zstack.core.progress.ProgressReportService.taskProgress; + +/** + * Placed after VmAllocateNicIpFlow in the flow chain. + * + * For each NIC belonging to an SDN-managed L2 network, this flow delegates + * to the registered {@link AfterAllocateSdnNicExtensionPoint} implementations + * (typically {@code SdnControllerManagerImpl}) which: + * + * - OVN: calls addLogicalPorts() with already-allocated IPs + * - ZNS: calls createSegmentPort(), receives IP back from ZNS, writes to DB + * - H3C/Sugon: default no-op + * + * Non-SDN NICs are skipped by the extension implementation internally. + */ +@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) +public class VmAllocateSdnNicFlow implements Flow { + private static final CLogger logger = Utils.getLogger(VmAllocateSdnNicFlow.class); + + @Autowired + private PluginRegistry pluginRgty; + + @Override + public void run(final FlowTrigger trigger, final Map data) { + taskProgress("create SDN ports for nics"); + + final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); + final List nics = spec.getDestNics(); + + if (nics == null || nics.isEmpty()) { + trigger.next(); + return; + } + + List exts = + pluginRgty.getExtensionList(AfterAllocateSdnNicExtensionPoint.class); + if (exts.isEmpty()) { + trigger.next(); + return; + } + + new While<>(exts).each((ext, wcomp) -> { + ext.afterAllocateSdnNic(spec, nics, new Completion(wcomp) { + @Override + public void success() { + wcomp.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + wcomp.addError(errorCode); + wcomp.allDone(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (!errorCodeList.getCauses().isEmpty()) { + trigger.fail(errorCodeList.getCauses().get(0)); + } else { + trigger.next(); + } + } + }); + } + + @Override + public void rollback(final FlowRollback chain, Map data) { + final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); + final List nics = spec.getDestNics(); + + List exts = + pluginRgty.getExtensionList(AfterAllocateSdnNicExtensionPoint.class); + + if (exts.isEmpty() || nics == null || nics.isEmpty()) { + chain.rollback(); + return; + } + + new While<>(exts).each((ext, wcomp) -> { + ext.rollbackSdnNic(spec, nics, new Completion(wcomp) { + @Override + public void success() { + wcomp.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + // best-effort: log and continue + logger.warn(String.format("failed to rollback SDN nic: %s", errorCode)); + wcomp.done(); + } + }); + }).run(new WhileDoneCompletion(chain) { + @Override + public void done(ErrorCodeList errorCodeList) { + chain.rollback(); + } + }); + } +} diff --git a/compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java b/compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java index 5b2dd2f399a..07247d60b91 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmCascadeExtension.java @@ -300,8 +300,8 @@ protected List handleDeletionForIpRange(List handleDeletionForIpRange(List handleDeletionForIpRange(List(nic.getUsedIps()).all((ip, comp) -> { ReturnIpMsg msg = new ReturnIpMsg(); msg.setUsedIpUuid(ip.getUuid()); @@ -82,9 +98,51 @@ public void run(MessageReply reply) { }).run(new WhileDoneCompletion(trigger){ @Override public void done(ErrorCodeList errorCodeList) { - dbf.removeByPrimaryKey(nic.getUuid(), VmNicVO.class); - trigger.next(); + callReleaseSdnNics(java.util.Collections.singletonList(nic), new Completion(trigger) { + @Override + public void success() { + dbf.removeByPrimaryKey(nic.getUuid(), VmNicVO.class); + trigger.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("releaseSdnNics failed for nic[uuid:%s]: %s, continue", + nic.getUuid(), errorCode)); + dbf.removeByPrimaryKey(nic.getUuid(), VmNicVO.class); + trigger.next(); + } + }); + } + }); + } + + private void callReleaseSdnNics(List nics, Completion completion) { + List exts = pluginRgty.getExtensionList(AfterAllocateSdnNicExtensionPoint.class); + if (exts.isEmpty()) { + completion.success(); + return; + } + + new While<>(exts).each((ext, wcomp) -> { + ext.releaseSdnNics(nics, new Completion(wcomp) { + @Override + public void success() { + wcomp.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("releaseSdnNics extension failed: %s, continue", errorCode)); + wcomp.done(); + } + }); + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errorCodeList) { + completion.success(); } }); } + } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java index 73f3e98b0df..4e904d6dbf4 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java @@ -2287,6 +2287,7 @@ void rollback() { flowChain.then(new VmAllocateNicFlow()); flowChain.then(new VmAllocateNicIpFlow()); + flowChain.then(new VmAllocateSdnNicFlow()); flowChain.then(new VmSetDefaultL3NetworkOnAttachingFlow()); setAdditionalFlow(flowChain, spec); if (self.getState() == VmInstanceState.Running) { diff --git a/compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java b/compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java index 31b3e35d32a..4a2ddd2a8be 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java @@ -295,10 +295,17 @@ public VmNicType getVmNicType(String vmUuid, L3NetworkInventory l3nw) { logger.debug(String.format("create %s on l3 network[uuid:%s] inside VmAllocateNicFlow", enableSriov ? "vf nic" : "vnic", l3nw.getUuid())); boolean enableVhostUser = NetworkServiceGlobalConfig.ENABLE_VHOSTUSER.value(Boolean.class); + + boolean enableDpdkVhostuser = Q.New(SystemTagVO.class) + .eq(SystemTagVO_.resourceType, VmInstanceVO.class.getSimpleName()) + .eq(SystemTagVO_.resourceUuid, vmUuid) + .eq(SystemTagVO_.tag, String.format("enableDpdkVhostuser::%s", l3nw.getUuid())) + .isExists(); + VmNicType.VmNicSubType subType = VmNicType.VmNicSubType.NONE; if (enableSriov) { subType = VmNicType.VmNicSubType.SRIOV; - } else if (enableVhostUser) { + } else if (enableVhostUser || enableDpdkVhostuser) { subType = VmNicType.VmNicSubType.VHOSTUSER; } L2NetworkVO l2nw = dbf.findByUuid(l3nw.getL2NetworkUuid(), L2NetworkVO.class); diff --git a/compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java index 0fd0e2aa068..0dcd047149f 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmReturnReleaseNicFlow.java @@ -6,10 +6,13 @@ 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.header.core.Completion; import org.zstack.header.core.WhileDoneCompletion; import org.zstack.header.core.workflow.FlowTrigger; import org.zstack.header.core.workflow.NoRollbackFlow; +import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.ErrorCodeList; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l3.L3NetworkConstant; @@ -34,6 +37,10 @@ public class VmReturnReleaseNicFlow extends NoRollbackFlow { protected CloudBus bus; @Autowired protected VmInstanceDeletionPolicyManager deletionPolicyMgr; + @Autowired + protected VmInstanceManager vmMgr; + @Autowired + protected PluginRegistry pluginRgty; @Override public void run(FlowTrigger chain, Map data) { @@ -43,6 +50,11 @@ public void run(FlowTrigger chain, Map data) { return; } + returnIpsAndReleaseNics(spec, data, chain); + } + + + private void returnIpsAndReleaseNics(VmInstanceSpec spec, Map data, FlowTrigger chain) { List msgs = new ArrayList<>(spec.getVmInventory().getVmNics().size()); for (VmNicInventory nic : spec.getVmInventory().getVmNics()) { for (UsedIpInventory ip : nic.getUsedIps()) { @@ -66,12 +78,14 @@ public void run(MessageReply reply) { })).run(new WhileDoneCompletion(chain) { @Override public void done(ErrorCodeList errorCodeList) { + List releasedNics = new ArrayList<>(); + List nicsToDelete = new ArrayList<>(); for (VmNicInventory nic : spec.getVmInventory().getVmNics()) { VmNicVO vo = dbf.findByUuid(nic.getUuid(), VmNicVO.class); if (VmInstanceConstant.USER_VM_TYPE.equals(spec.getVmInventory().getType())) { VmInstanceDeletionPolicy deletionPolicy = getDeletionPolicy(spec, data); if (deletionPolicy == VmInstanceDeletionPolicy.Direct) { - dbf.remove(vo); + nicsToDelete.add(vo); } else { vo.setUsedIpUuid(null); vo.setIp(null); @@ -80,10 +94,53 @@ public void done(ErrorCodeList errorCodeList) { dbf.update(vo); } } else { - dbf.remove(vo); + nicsToDelete.add(vo); } + releasedNics.add(nic); } - chain.next(); + + callReleaseSdnNics(releasedNics, new Completion(chain) { + @Override + public void success() { + nicsToDelete.forEach(dbf::remove); + chain.next(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("releaseSdnNics failed: %s, continue anyway", errorCode)); + nicsToDelete.forEach(dbf::remove); + chain.next(); + } + }); + } + }); + } + + private void callReleaseSdnNics(List nics, Completion completion) { + List exts = pluginRgty.getExtensionList(AfterAllocateSdnNicExtensionPoint.class); + if (exts.isEmpty() || nics.isEmpty()) { + completion.success(); + return; + } + + new While<>(exts).each((ext, wcomp) -> { + ext.releaseSdnNics(nics, new Completion(wcomp) { + @Override + public void success() { + wcomp.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + logger.warn(String.format("releaseSdnNics extension failed: %s, continue", errorCode)); + wcomp.done(); + } + }); + }).run(new WhileDoneCompletion(completion) { + @Override + public void done(ErrorCodeList errorCodeList) { + completion.success(); } }); } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java b/compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java index 713c64890ee..01d63c40464 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmSystemTags.java @@ -5,6 +5,7 @@ import org.zstack.header.tag.AdminOnlyTag; import org.zstack.header.tag.TagDefinition; import org.zstack.header.vm.VmInstanceVO; +import org.zstack.header.vm.VmNicVO; import org.zstack.tag.PatternedSystemTag; import org.zstack.tag.SensitiveTagOutputHandler; import org.zstack.tag.SensitiveTag; @@ -314,4 +315,8 @@ public String desensitizeTag(SystemTag systemTag, String tag) { public static PatternedSystemTag VM_STATE_PAUSED_AFTER_MIGRATE = new PatternedSystemTag(("vmPausedAfterMigrate"), VmInstanceVO.class); public static PatternedSystemTag VM_MEMORY_ACCESS_MODE_SHARED = new PatternedSystemTag(("vmMemoryAccessModeShared"), VmInstanceVO.class); + + public static String IFACE_ID_TOKEN = "ifaceId"; + public static PatternedSystemTag IFACE_ID = new PatternedSystemTag( + String.format("ifaceId::{%s}", IFACE_ID_TOKEN), VmNicVO.class); } diff --git a/conf/db/upgrade/V5.5.18__schema.sql b/conf/db/upgrade/V5.5.18__schema.sql new file mode 100644 index 00000000000..84d643113a4 --- /dev/null +++ b/conf/db/upgrade/V5.5.18__schema.sql @@ -0,0 +1,31 @@ +-- ZNS SDN Controller support + +CREATE TABLE IF NOT EXISTS `ZnsControllerVO` ( + `uuid` varchar(32) NOT NULL, + PRIMARY KEY (`uuid`), + CONSTRAINT `fkZnsControllerVOSdnControllerVO` FOREIGN KEY (`uuid`) REFERENCES `SdnControllerVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `L2GeneveNetworkVO` ( + `uuid` varchar(32) NOT NULL, + `geneveId` int(10) unsigned NOT NULL, + PRIMARY KEY (`uuid`), + CONSTRAINT `fkL2GeneveNetworkVOL2NetworkEO` FOREIGN KEY (`uuid`) REFERENCES `L2NetworkEO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `ZnsTransportZoneVO` ( + `uuid` varchar(32) NOT NULL, + `name` varchar(255) DEFAULT NULL, + `description` varchar(2048) DEFAULT NULL, + `type` varchar(64) DEFAULT NULL, + `physicalNetwork` varchar(255) DEFAULT NULL, + `status` varchar(64) DEFAULT NULL, + `isDefault` tinyint(1) NOT NULL DEFAULT 0, + `tags` text DEFAULT NULL, + `znsSdnControllerUuid` varchar(32) NOT NULL, + `createDate` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `lastOpDate` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + CONSTRAINT `fkZnsTransportZoneVOSdnControllerVO` FOREIGN KEY (`znsSdnControllerUuid`) REFERENCES `SdnControllerVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + diff --git a/conf/springConfigXml/VmInstanceManager.xml b/conf/springConfigXml/VmInstanceManager.xml index ef3d5a7cc9e..0ab2073d583 100755 --- a/conf/springConfigXml/VmInstanceManager.xml +++ b/conf/springConfigXml/VmInstanceManager.xml @@ -37,6 +37,7 @@ org.zstack.compute.vm.VmAllocateVolumeFlow org.zstack.compute.vm.VmAllocateNicFlow org.zstack.compute.vm.VmAllocateNicIpFlow + org.zstack.compute.vm.VmAllocateSdnNicFlow org.zstack.compute.vm.VmAllocateCdRomFlow org.zstack.compute.vm.VmInstantiateResourcePreFlow org.zstack.compute.vm.VmCreateOnHypervisorFlow diff --git a/conf/springConfigXml/sdnController.xml b/conf/springConfigXml/sdnController.xml index a9be9ec8795..e76be084ac6 100644 --- a/conf/springConfigXml/sdnController.xml +++ b/conf/springConfigXml/sdnController.xml @@ -26,14 +26,11 @@ - - - - + diff --git a/core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java b/core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java new file mode 100644 index 00000000000..f719bb8445e --- /dev/null +++ b/core/src/main/java/org/zstack/core/rest/webhook/WebhookCallbackClient.java @@ -0,0 +1,189 @@ +package org.zstack.core.rest.webhook; + +import org.zstack.core.Platform; +import org.zstack.core.thread.ThreadFacade; +import org.zstack.core.thread.ThreadFacadeImpl; +import org.zstack.header.core.ReturnValueCompletion; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.rest.RESTFacade; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static org.zstack.core.Platform.operr; + +/** + * Generic async callback client for external systems that use a webhook pattern: + *
    + *
  1. Send an HTTP request to the external system
  2. + *
  3. External system returns immediately (e.g. 202 Accepted)
  4. + *
  5. External system later POSTs back the result to a callback URL
  6. + *
  7. This client matches the callback to the original request and completes it
  8. + *
+ * + *

Callback flow:

+ *
+ * External Controller
+ *     │  POST /asyncrest/sendcommand  (commandpath header → callbackPath)
+ *     ▼
+ * AsyncRESTCallController.sendCommand()
+ *     ▼
+ * RESTFacadeImpl.sendCommand()  →  httpCallhandlers.get(callbackPath)
+ *     ▼
+ * WebhookCallbackClient.onCallback(T cmd)
+ *     ├─ protocol.extractTaskId(cmd)
+ *     ├─ pendingCalls.remove(taskId)  ← CAS point (atomic, prevents double invocation)
+ *     ├─ cancel timeout
+ *     └─ protocol.isSuccess(cmd) ? completion.success(cmd) : completion.fail(error)
+ * 
+ * + *

This class is a plain POJO — not a Spring bean. It is created and owned by the + * plugin-specific API client (e.g. ZnsApiClient) which passes in its dependencies.

+ * + *

Thread safety: {@code ConcurrentHashMap.remove()} serves as the CAS point. + * Among callback arrival, timeout, and send-failure, only one can successfully remove + * and thus complete a pending call.

+ * + * @param the callback body type + */ +public class WebhookCallbackClient { + private static final CLogger logger = Utils.getLogger(WebhookCallbackClient.class); + + private final WebhookProtocol protocol; + private final RESTFacade restf; + private final ThreadFacade thdf; + private final ConcurrentHashMap> pendingCalls = new ConcurrentHashMap<>(); + private String callbackUrl; + + private static class PendingEntry { + final ReturnValueCompletion completion; + final ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt; + + PendingEntry(ReturnValueCompletion completion, + ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt) { + this.completion = completion; + this.timeoutReceipt = timeoutReceipt; + } + } + + public WebhookCallbackClient(WebhookProtocol protocol, RESTFacade restf, ThreadFacade thdf) { + this.protocol = protocol; + this.restf = restf; + this.thdf = thdf; + } + + /** + * Register the callback handler on the sendCommand channel. + * Must be called once during the owning component's start() lifecycle. + */ + public void start() { + this.callbackUrl = restf.getSendCommandUrl(); + restf.registerSyncHttpCallHandler( + protocol.getCallbackPath(), + protocol.getCallbackClass(), + this::onCallback); + } + + /** + * Register a pending call and return its task identifier. + * + *

The caller should use the returned taskId to decorate the outgoing request + * headers via {@link WebhookProtocol#decorateRequest}, then send the HTTP request. + * If the send fails, the caller must invoke {@link #fail} to clean up.

+ * + * @param completion the completion to invoke when the callback arrives (or on timeout) + * @param unit timeout time unit + * @param timeout timeout value + * @return the generated taskId + */ + public String submit(ReturnValueCompletion completion, TimeUnit unit, long timeout) { + String taskId = Platform.getUuid(); + + ThreadFacadeImpl.TimeoutTaskReceipt timeoutReceipt = thdf.submitTimeoutTask(() -> { + fail(taskId, operr("[Webhook Timeout] callback timed out for taskId[%s], path[%s]", + taskId, protocol.getCallbackPath())); + }, unit, timeout); + + pendingCalls.put(taskId, new PendingEntry<>(completion, timeoutReceipt)); + return taskId; + } + + /** + * Actively fail a pending call (e.g. when the HTTP send fails). + * + *

{@code ConcurrentHashMap.remove()} is atomic — only one of + * (callback / timeout / send-failure) can win, preventing double invocation.

+ */ + public void fail(String taskId, ErrorCode error) { + PendingEntry entry = pendingCalls.remove(taskId); + if (entry != null) { + entry.timeoutReceipt.cancel(); + entry.completion.fail(error); + } + } + + /** + * @return the callback URL that the external system should POST results to + */ + public String getCallbackUrl() { + return callbackUrl; + } + + /** + * Override the callback URL. Use this when the callback is handled by a + * dedicated HTTP endpoint (e.g. a Spring Controller) rather than the + * sendCommand channel. + */ + public void setCallbackUrl(String callbackUrl) { + this.callbackUrl = callbackUrl; + } + + /** + * @return the protocol adapter + */ + public WebhookProtocol getProtocol() { + return protocol; + } + + /** + * Deliver a callback that was received outside the sendCommand channel + * (e.g. from a dedicated Spring Controller endpoint for external systems). + */ + public void deliverCallback(T cmd) { + onCallback(cmd); + } + + /** + * Callback handler invoked by the RESTFacade sendCommand channel. + */ + private String onCallback(T cmd) { + String taskId = protocol.extractTaskId(cmd); + if (taskId == null) { + logger.warn(String.format("received webhook callback without taskId on path[%s], ignoring", + protocol.getCallbackPath())); + return null; + } + + PendingEntry entry = pendingCalls.remove(taskId); + if (entry == null) { + logger.warn(String.format("received webhook callback for unknown taskId[%s] on path[%s], ignoring", + taskId, protocol.getCallbackPath())); + return null; + } + + entry.timeoutReceipt.cancel(); + + if (protocol.isSuccess(cmd)) { + entry.completion.success(cmd); + } else { + String error = protocol.extractError(cmd); + entry.completion.fail(operr("webhook callback failed for taskId[%s], path[%s], error: %s", + taskId, protocol.getCallbackPath(), error != null ? error : "unknown")); + } + + return null; + } +} + diff --git a/core/src/main/java/org/zstack/core/rest/webhook/WebhookProtocol.java b/core/src/main/java/org/zstack/core/rest/webhook/WebhookProtocol.java new file mode 100644 index 00000000000..c644fc58f19 --- /dev/null +++ b/core/src/main/java/org/zstack/core/rest/webhook/WebhookProtocol.java @@ -0,0 +1,55 @@ +package org.zstack.core.rest.webhook; + +import java.util.Map; + +/** + * Defines the protocol adaptation for an external system's webhook callback mechanism. + * + *

Different external controllers (e.g., SDN controllers) use different header conventions, + * callback body formats, and success/failure semantics. This interface abstracts those + * differences so that {@link WebhookCallbackClient} can handle the common async lifecycle + * (pending call registration, timeout, CAS-guarded callback dispatch) generically.

+ * + * @param the callback body type that the external system POSTs back + */ +public interface WebhookProtocol { + + /** + * The path to register on the sendCommand channel (e.g. "/zns/callback"). + * This will be passed to {@code RESTFacade.registerSyncHttpCallHandler}. + */ + String getCallbackPath(); + + /** + * The class used to deserialize the callback JSON body. + */ + Class getCallbackClass(); + + /** + * Extract the task identifier from the callback body. + * This must match the taskId returned by {@link WebhookCallbackClient#submit}. + */ + String extractTaskId(T callback); + + /** + * Determine whether the callback indicates a successful operation. + */ + boolean isSuccess(T callback); + + /** + * Extract a human-readable error description from the callback body. + * Called only when {@link #isSuccess} returns false. + */ + String extractError(T callback); + + /** + * Decorate the outgoing HTTP request headers with the task identifier and callback URL, + * following the conventions of the external system. + * + * @param headers mutable map to add headers to + * @param taskId the unique task identifier for this async call + * @param callbackUrl the URL the external system should POST the result to + */ + void decorateRequest(Map headers, String taskId, String callbackUrl); +} + diff --git a/docs/modules/network/nav.adoc b/docs/modules/network/nav.adoc index 53e42d44f22..189d4db5edb 100644 --- a/docs/modules/network/nav.adoc +++ b/docs/modules/network/nav.adoc @@ -3,4 +3,5 @@ *** xref:networkResource/L2Network.adoc[] *** xref:networkResource/L3Network.adoc[] *** xref:networkResource/VpcRouter.adoc[] + *** xref:networkResource/ZnsIntegration.adoc[] ** xref:networkService/networkService.adoc[] \ No newline at end of file diff --git a/docs/modules/network/pages/networkResource/ZStackL2NetworkType.adoc b/docs/modules/network/pages/networkResource/ZStackL2NetworkType.adoc new file mode 100644 index 00000000000..14c22fcda29 --- /dev/null +++ b/docs/modules/network/pages/networkResource/ZStackL2NetworkType.adoc @@ -0,0 +1,504 @@ += 网络类型体系:L2NetworkType / VSwitchType / VmNicType + +ZStack 的网络类型由三层模型组成,分别描述二层网络拓扑、虚拟交换机实现和虚拟机网卡类型。 + +== 三层类型模型 + +[cols="1,1,1"] +|=== +| L2NetworkType (网络拓扑类型) | VSwitchType (虚拟交换机类型) | VmNicType (网卡类型) + +| 定义二层网络的拓扑结构 +| 定义物理机上的虚拟交换机实现 +| 定义虚拟机网卡的驱动/接口类型 + +| 由 `L2NetworkFactory` 注册 +| 由 `VmInstanceNicFactory` 注册 +| 由 `VSwitchType.addVmNicType()` 绑定 + +| 存储在 `L2NetworkVO.type` +| 存储在 `L2NetworkVO.vSwitchType` +| 存储在 `VmNicVO.type` +|=== + +=== 关系 + +---- +L2NetworkType 1 ──── N VSwitchType 1 ──── N VmNicType + (一种L2可选多种vSwitch) (一种vSwitch可有多种NIC子类型) +---- + +例: + +---- +L2NoVlanNetwork ──┬── LinuxBridge ──── VIRTUAL_NIC (SubType=NONE) + └── OVN_DPDK ──┬── dpdkvhostuserclient (SubType=NONE) + └── VF (SubType=SRIOV) + +L2GeneveNetwork ──── ZNS ──┬── VIRTUAL_NIC (SubType=NONE, 默认) + └── dpdkvhostuserclient (SubType=VHOSTUSER, 需system tag指定) + +L2VlanNetwork ──── LinuxBridge ──── VIRTUAL_NIC (SubType=NONE) +---- + +== 注册机制 + +=== L2NetworkType + +在 `L2NetworkFactory.getType()` 中通过 `new L2NetworkType("xxx")` 自动注册到静态 Map。 + +[source,java] +---- +// L2NoVlanL2NetworkFactory.java +public L2NetworkType getType() { + return new L2NetworkType(L2NetworkConstant.L2_NO_VLAN_NETWORK_TYPE); +} +---- + +=== VSwitchType + +在 `VmInstanceNicFactory.getType()` 中通过 `new VSwitchType("xxx")` 自动注册到静态 Map。 + +[source,java] +---- +// ZnsVmNicFactory.java +static final VSwitchType vSwitchType = new VSwitchType("ZNS"); +---- + +=== VmNicType + +通过 `VSwitchType.addVmNicType(SubType, VmNicType)` 绑定到 VSwitchType。 + +[source,java] +---- +// ZnsVmNicFactory.java +vSwitchType.addVmNicType(VmNicType.VmNicSubType.NONE, type); +---- + +== 全部 L2NetworkType(10种) + +[cols="2,3,2"] +|=== +| L2NetworkType | 工厂类 | 模块 + +| L2NoVlanNetwork +| `L2NoVlanL2NetworkFactory` +| network/ + +| L2VlanNetwork +| `L2VlanNetworkFactory` +| network/ + +| VxlanNetwork +| `VxlanNetworkFactory` +| plugin/vxlan/ + +| VxlanNetworkPool +| `VxlanNetworkPoolFactory` +| plugin/vxlan/ + +| HardwareVxlanNetwork +| `HardwareVxlanNetworkFactory` +| plugin/sdnController/ + +| HardwareVxlanNetworkPool +| `HardwareVxlanNetworkPoolFactory` +| plugin/sdnController/ + +| L2GeneveNetwork +| `L2GeneveNetworkFactory` +| premium/zns/ + +| PortGroupNetwork +| `L2PortGroupNetworkFactory` +| premium/virtualSwitch/ + +| VirtualSwitchNetwork +| `L2VirtualSwitchNetworkFactory` +| premium/virtualSwitch/ + +| TfNetwork +| `TfL2NetworkFactory` +| plugin/sugonSdn/ +|=== + +== 全部 VSwitchType(7种) + +[cols="2,1,1,2"] +|=== +| VSwitchType | sdnControllerType | nicLifecycleManagedByFactory | NicFactory + +| LinuxBridge +| null +| false +| `VmNicFactory` + +| ZNS +| "ZNS" +| *true* +| `ZnsVmNicFactory` + +| MacVlan +| null +| false +| `VmMacVlanNicFactory` + +| OVN_DPDK +| null +| false +| `VmOvnVhostUserNicFactory` + +| OVS_DPDK +| null +| false +| `VmOvnVhostUserNicFactory` + +| TF +| null +| false +| `TfVmNicFactory` + +| OVS_DPDK(vDPA) +| null +| false +| `VmVdpaNicFactory` +|=== + +=== VSwitchType 关键属性 + +[cols="2,4,1"] +|=== +| 属性 | 作用 | ZNS 值 + +| `sdnControllerType` +| 关联的 SDN 控制器类型,null 表示无 SDN +| "ZNS" + +| `nicLifecycleManagedByFactory` +| NIC 生命周期由 NicFactory 管理 (true) 还是 SdnControllerManager 管理 (false) +| *true* + +| `attachToCluster` +| 是否需要手动挂载集群 +| false(ZNS 通过 TransportZone 自动挂载) + +| `useDpdk` +| 是否使用 DPDK +| false +|=== + +== L2NetworkType → VSwitchType 映射 + +L2NetworkVO 同时持有 `type`(L2NetworkType)和 `vSwitchType`(VSwitchType)。 +L2 创建时由用户或系统指定 vSwitchType。同一 L2NetworkType 可搭配不同 VSwitchType: + +[cols="2,2,3"] +|=== +| L2NetworkType | 可用 VSwitchType | 说明 + +| L2NoVlanNetwork +| LinuxBridge, OVN_DPDK, OVS_DPDK, ZNS +| 普通无 VLAN 二层网络 + +| L2VlanNetwork +| LinuxBridge, OVN_DPDK, OVS_DPDK, ZNS +| VLAN tagged 网络 + +| VxlanNetwork +| LinuxBridge +| 软件 VXLAN 覆盖网络 + +| HardwareVxlanNetwork +| (SDN 专用) +| 硬件 SDN VXLAN + +| L2GeneveNetwork +| ZNS +| ZNS Geneve 覆盖网络 + +| PortGroupNetwork +| (VirtualSwitch) +| VMware 端口组 + +| TfNetwork +| TF +| Tungsten Fabric +|=== + +== VSwitchType → VmNicType 映射(按 VmNicSubType) + +请求创建 NIC 时: + +. 检查 VM 是否有 SRIOV 系统标签 → SubType=SRIOV +. 检查全局配置是否启用 VHOSTUSER → SubType=VHOSTUSER +. 否则 → SubType=NONE + +`VSwitchType.getVmNicType(subType)` 从 nicTypes Map 中查找对应的 VmNicType。 + +[cols="2,1,1,1"] +|=== +| VSwitchType | SubType=NONE | SubType=SRIOV | SubType=VHOSTUSER + +| LinuxBridge +| VIRTUAL_NIC +| — +| — + +| ZNS +| VIRTUAL_NIC +| — +| dpdkvhostuserclient + +| MacVlan +| MACVLAN_NIC +| — +| — + +| OVN_DPDK +| OVN_VHOSTUSER +| OVN_VF +| — + +| OVS_DPDK +| — +| VDPA +| OVS_VHOSTUSER + +| TF +| TF_NIC +| — +| — +|=== + +== 创建 VM 时 NIC 组装完整流程 + +---- + VmAllocateNicFlow + │ + L3Network → L2Network → VSwitchType → VmNicType → VmInstanceNicFactory + │ + factory.createVmNic() + ┌───────────┴───────────┐ + │ │ + (持久化 VmNicVO) factory.afterCreateVmNic() + │ + ┌─────────────────────────┤ + │ │ + 普通 NIC (空操作) ZNS NIC: + createSegmentPort() + → 创建 UsedIpVO + → 回写 VmNicVO.ip/gateway + │ + VmInstantiateResourcePreFlow + │ + SdnControllerManagerImpl.preInstantiateVmResource() + │ + ┌──────────────┼──────────────┐ + │ │ │ + sdnControllerType nicLifecycle 其他 SDN: + == null? ManagedByFactory? controller.addVmNics() + → 跳过(普通) → 跳过(ZNS已处理) (OVN/HW-VXLAN 路径) + │ + KVMHost.completeNicInfo() + │ + KVMCompleteNicInformationExtensionPoint (按 L2Type 分发) + ┌──────┬──────┼──────┬──────┐ + │ │ │ │ │ + NoVlan Vlan VxLAN Geneve PortGroup + bridge bridge bridge bridge ... + 无VLAN +VLAN +VNI (ZNS管) + │ + StartVmCmd → KVM Agent +---- + +== L2 物理机配置(Realize)决策 + +[cols="2,3,3"] +|=== +| L2 Backend | realize() 行为 | 触发时机 + +| KVMRealizeL2NoVlan +| `CreateBridgeCmd` → 物理机创建网桥 +| Host 连接时 + NIC 挂载时 + +| KVMRealizeL2Vlan +| `CreateVlanBridgeCmd` → 创建 VLAN 子接口 + 网桥 +| Host 连接时 + NIC 挂载时 + +| KVMRealizeL2Vxlan +| `CreateVxlanBridgeCmd` → 创建 VXLAN 隧道 + 网桥 +| 按需(`InstantiateResourceOnAttachingNic`) + +| KVMRealizeL2Geneve +| *空操作* +| ZNS/OVS controller 自行管理网桥 +|=== + +== 全部 VmNicType(6种) + +[cols="2,2,2,3"] +|=== +| VmNicType | 类型名 | NicFactory | 说明 + +| VNIC +| `"VNIC"` +| `VmNicFactory` +| 标准 virtio 网卡,最常用的类型 + +| dpdkvhostuserclient +| `"dpdkvhostuserclient"` +| `VmOvnVhostUserNicFactory` +| DPDK vhost-user 网卡,使用 OVS 的 vhost-user socket 进行高性能数据传输 + +| VF +| `"VF"` +| `VmVfNicFactory` +| SR-IOV Virtual Function 网卡,直通物理网卡的虚拟功能 + +| vDPA +| `"vDPA"` +| `VmVdpaNicFactory` +| vDPA (virtio Data Path Acceleration) 网卡,硬件加速的 virtio 数据路径 + +| MACVLAN +| `"MACVLAN"` +| `VmMacVlanNicFactory` +| MacVLAN 网卡,基于 MAC 地址的虚拟 LAN + +| TFVNIC +| `"TFVNIC"` +| `TfVmNicFactory` +| Tungsten Fabric 网卡 +|=== + +=== 各 VmNicType 的使用条件 + +VmNicType 的选择由 `VmNicManagerImpl.getVmNicType()` 决定,依据三个因素: + +. L2 网络的 VSwitchType +. VM 是否有 SRIOV 系统标签(`enableSRIOV::{l3Uuid}`) +. 全局配置 `ENABLE_VHOSTUSER` 是否开启 + +选择优先级:SRIOV > VHOSTUSER > NONE,若 VSwitchType 不支持请求的 SubType,回退到 NONE。 + +[cols="2,2,2,2,2"] +|=== +| VmNicType | VSwitchType | L2NetworkType | VmNicSubType | 使用条件 + +| VNIC +| LinuxBridge +| L2NoVlanNetwork, L2VlanNetwork +| NONE +| 默认情况:未启用 SRIOV 且未启用 VHOSTUSER + +| dpdkvhostuserclient +| OVN_DPDK +| L2NoVlanNetwork +| NONE +| L2 使用 OVN_DPDK vSwitch,默认情况 + +| dpdkvhostuserclient +| OVS_DPDK +| L2NoVlanNetwork, L2VlanNetwork +| VHOSTUSER +| L2 使用 OVS_DPDK vSwitch 且全局配置 `ENABLE_VHOSTUSER=true` + +| dpdkvhostuserclient +| OVN_DPDK +| L2NoVlanNetwork +| SRIOV +| L2 使用 OVN_DPDK vSwitch 且 VM 有 `enableSRIOV` 标签(注册为 VF 类型) + +| VF +| LinuxBridge +| L2NoVlanNetwork, L2VlanNetwork +| SRIOV +| L2 使用 LinuxBridge 且 VM 有 `enableSRIOV` 标签 + +| vDPA +| OVS_DPDK +| L2NoVlanNetwork, L2VlanNetwork +| NONE, SRIOV +| L2 使用 OVS_DPDK vSwitch,默认或 SRIOV 模式 + +| MACVLAN +| MacVlan +| L2NoVlanNetwork, L2VlanNetwork +| NONE +| L2 使用 MacVlan vSwitch + +| VNIC +| ZNS +| L2NoVlanNetwork, L2VlanNetwork, L2GeneveNetwork +| NONE +| L2 使用 ZNS vSwitch,默认网卡类型 + +| dpdkvhostuserclient +| ZNS +| L2NoVlanNetwork, L2VlanNetwork, L2GeneveNetwork +| VHOSTUSER +| L2 使用 ZNS vSwitch,用户通过 system tag 指定使用 dpdkvhostuserclient + +| TFVNIC +| TF +| TfNetwork +| NONE +| L2 使用 TF (Tungsten Fabric) vSwitch +|=== + +=== NicTO 中 srcPath 设置规则 + +当 VSwitchType 为 OVN_DPDK 或 ZNS 时,`completeNicInformation()` 会设置 +`srcPath = "/var/run/openvswitch/" + nic.getInternalName()`,用于 DPDK vhost-user socket 连接。 + +[cols="2,2,2,1"] +|=== +| L2NetworkType | VSwitchType | 设置 srcPath? | Backend + +| L2NoVlanNetwork +| LinuxBridge +| 否 +| KVMRealizeL2NoVlanNetworkBackend + +| L2NoVlanNetwork +| OVN_DPDK / ZNS +| *是* +| KVMRealizeL2NoVlanNetworkBackend + +| L2VlanNetwork +| LinuxBridge +| 否 +| KVMRealizeL2VlanNetworkBackend + +| L2VlanNetwork +| OVN_DPDK / ZNS +| *是* +| KVMRealizeL2VlanNetworkBackend + +| L2GeneveNetwork +| ZNS +| 否(仅 OVN_DPDK 设置) +| KVMRealizeL2GeneveNetworkBackend + +| VxlanNetwork +| LinuxBridge +| 否 +| KVMRealizeL2VxlanNetworkBackend +|=== + +== SDN 两条路径对比 + +=== Path A: Factory 管理(ZNS) + +* `VSwitchType.nicLifecycleManagedByFactory = true` +* `SdnControllerManagerImpl.preInstantiateVmResource()` 跳过 +* `VmInstanceNicFactory`(ZnsVmNicFactory)在 `afterCreateVmNic()` 中直接调 ZNS API +* Port 在 VM stop/reboot 时持久化,不随 VM 生命周期反复创建/删除 + +=== Path B: SdnControllerManager 管理(OVN / Hardware VXLAN) + +* `VSwitchType.nicLifecycleManagedByFactory = false`,`sdnControllerType != null` +* `SdnControllerManagerImpl.preInstantiateVmResource()` 处理 +* 通过 `L2NetworkSystemTags.SdnControllerUuid` 找到控制器 UUID +* 调用 `SdnControllerL2.addVmNics()` 创建端口 +* Port 随 VM start/stop 反复创建/删除 diff --git a/docs/modules/network/pages/networkResource/ZnsIntegration.adoc b/docs/modules/network/pages/networkResource/ZnsIntegration.adoc new file mode 100644 index 00000000000..b536d694109 --- /dev/null +++ b/docs/modules/network/pages/networkResource/ZnsIntegration.adoc @@ -0,0 +1,911 @@ += 对接ZNS SDN控制器 + +== 总体描述 + +在考虑对接 ZNS SDN 控制器时,首先需要考虑如何做好 Cloud 资源对象和 ZNS 资源映射。 + +* ZNS segments → L2Network + L3Network + IpRange +* ZNS segments port → VmNic + UsedIp +* ZNS segments + transport zone → L2NetworkClusterRefVO + +=== CMS +Cloud L2 和 ZNS segment 之间的资源如何一一对应? +引入一个 CMS 数据结构。 + +[source,go] +---- + type Cms struct { + CmsUuid string + Type string ### cloud/zsv/zaku/zns + IP string ### cloud mn vip + Role string ###owner, user + CmsResourceUuid string ###owner, user +} + +type Segment { + ... + CmsMetaDatas []Cms `json:"cms"` +} +---- +* Cms::Type 定义 cms 类型:Cloud、ZSV、ZAKU、ZNS +* Cms::IP 定义 cms 的 IP 地址,该字段主要为了让维护人员更友好地识别资源,cms 的唯一标识仍是 CmsUuid +* Cms::CmsUuid 定义 cmsUuid。该值是 ZNS 上创建的 Computer Manager UUID,Cloud 在创建 SdnController 时会把这个 UUID 记录到 SdnController 的 systemTag 中 +* Cms::Role 定义 cms 是这个资源的 owner 还是 user。owner 表示该 cms 创建了资源,user 表示该 cms 使用了资源。一个资源可以在多个 cms 之间共享,比如一个 segment 可以被多个 Cloud 使用。资源删除有两种策略: +** 只有当所有 cms 都不使用该资源时,才可以删除(以 CmsMetaDatas 作为资源引用基数); +** 只要 owner 删除了该资源,就删除该资源。必须等待 user 删除后,owner 才能删除 +* Cms::CmsResourceUuid 定义该 cms 系统对应的资源 UUID,在数据同步过程中用于映射 Cms 资源和 ZNS 资源。 + +=== ZNS API + +ZNS API 定义需要包含 Cms 数据: + +* 创建 API:cms 根据自己的资源创建 cms 数据,ZNS 存入数据库 +* 删除 API:cms 根据资源 UUID 删除对应的 cms 数据,ZNS 根据结果执行删除操作 +* 查询 API:需要支持根据 cmsUuid 过滤资源 +* 修改操作者 API:ZNS 可以提供 API 添加/删除资源 user + +[NOTE] +不是所有 API 都需要以上全部操作。 + +=== ZNS 数据库对象 + +不是所有资源对象都需要保持 cms 信息,但是由 cms 创建的资源,或者需要按 cms 查询的资源,都需要保存 cms 信息。 +一个资源可能有多个 cms 信息,表示资源可在多个 cms 之间共享。 + +=== UUID 格式约定 + +ZNS 使用带连字符的 UUID 格式(`550e8400-e29b-41d4-a716-446655440000`),Cloud 使用不带连字符的紧凑 UUID 格式(`550e8400e29b41d4a716446655440000`)。 + +Cloud 在调用 ZNS API 时需要将 Cloud UUID 转换为 ZNS UUID 格式,在接收 ZNS 响应时需要将 ZNS UUID 转换回 Cloud 格式。 + +== Cloud 与 ZNS 通信协议 + +=== 传输层 + +Cloud 通过 HTTP REST API 与 ZNS 通信。所有请求/响应体均为 JSON 格式。 + +全局配置参数: + +[cols="2,2,2"] +|=== +|配置项 |默认值 |说明 + +|`zns.controller.scheme` +|`http` +|HTTP 或 HTTPS + +|`zns.controller.port` +|`7278` +|ZNS API 端口 + +|`zns.controller.timeout` +|`300000`(5分钟) +|请求超时时间(毫秒) + +|=== + +=== 异步回调模式 + +所有写操作(POST/PATCH/DELETE)采用异步回调模式: + +.... +Cloud ZNS + │ POST/PATCH/DELETE + headers │ + │ x-web-hook: │ + │ x-job-uuid: │ + │─────────────────────────────────────────►│ + │ ◄── 202 Accepted │ (ZNS 立即返回) + │ │ + │ (ZNS 异步处理完成后回调 Cloud) │ + │ ◄── POST │ + │ { taskUuid, success, status, data } │ + │ │ + │ Cloud 根据 taskUuid 匹配到 │ + │ 对应的等待中的调用并完成 │ +.... + +所有读操作(GET)为同步调用,直接返回 200 + JSON body。 + +=== 请求串行化 + +对同一个 ZNS IP 的所有 API 调用通过任务链串行执行,避免并发操作导致的冲突。 + +== ZNS SDN控制器 + +ZStack 已经定义 `SdnControllerVO`,目前已有 `H3cVcfcSdnController`、`SugonSdnController`、`OvnController`、`HuaweiIMasterSdnController` 等实现。 + +新定义 `ZnsControllerVO`,继承 `SdnControllerVO`: + +* vendorType:ZNS +* vendorVersion:用于记录当前连接的 ZNS 版本(详见 <> 章节) +* transportZones:关联的 `ZnsTransportZoneVO` 列表(一对多) + +[NOTE] +必须先在 ZNS 完成添加 Computer Manager 的操作,然后在 Cloud 侧创建对应的 SdnController。 +ZNS SDN Controller 保持 SystemTags:`computerManagerUuid::xxxx`,这里的 xxxx 是 ZNS 创建的 Computer Manager UUID。 +后续 Cloud 调用 ZNS API 创建 segment、segment port 时,Cloud 会根据 computerManagerUuid 组装 cms 信息。 + +Cloud 还通过 SystemTag 记录以下映射关系: + +* `znsSegmentUuid::{segmentUuid}` 在 `L2NetworkVO` 上 — 映射 Cloud L2 → ZNS segment +* `znsSegmentPortUuid::{portUuid}` 在 `VmNicVO` 上 — 映射 Cloud NIC → ZNS segment port + +=== 创建SDN控制器 + +==== 1. 验证 Computer Manager + +根据 systemTag 中提供的 `computerManagerUuid`,调用 `GET /zns/api/v1/fabric/compute-managers/{uuid}` 验证 Computer Manager 在 ZNS 上存在且可用。 + +==== 2. 同步 Compute Collections(集群映射) + +根据 `GET /zns/api/v1/fabric/compute-collections` 获取 compute collection 列表,过滤出属于当前 Computer Manager 的条目。 + +ZNS 侧直接使用 Cloud 的 cluster UUID,因此可以直接按 UUID 匹配 Cloud 侧的 `ClusterVO`,无需 name 匹配。 + +构建以下映射关系: +* `znsClusterUuids`:属于该 Computer Manager 的 ZNS cluster UUID 集合 +* `znsClusterToZstackCluster`:ZNS cluster UUID → Cloud cluster UUID + +==== 3. 获取 Discovered Nodes(主机发现) + +根据 `GET /zns/api/v1/fabric/discovered-nodes` 获取 discovered node 列表(`HostData`),过滤条件:`clusterId` 属于步骤 2 中确定的 cluster 集合,且 `managementIp != null`: + +** `HostData.managementIp`:匹配 Cloud `HostVO.managementIp`,找到对应 `HostVO.uuid` +** `HostData.clusterId`:即 Cloud `ClusterVO.uuid` +** `HostData.transportNodeProfileId`:用于后续推导 vSwitchType 和建立 transport zone → cluster 的反向映射 + +[NOTE] +ZNS 侧需保证同一个 cluster 内所有 node 的 `transportNodeProfileId` 相同。 + +==== 4. 推导 vSwitchType 并创建 Host Ref + +根据 `HostData.transportNodeProfileId` 调用 `GET /zns/api/v1/fabric/transport-node-profiles/{uuid}` 获取 `TransportNodeProfileData`,取 `hostSwitchProfiles[0]` 调用 `GET /zns/api/v1/fabric/host-switch-profiles/{uuid}` 获取 `HostSwitchProfileData`: + +** `HostSwitchProfileData.type`:枚举值 `dpdk` 或 `kernel`,用于推导 `SdnControllerHostRefVO.vSwitchType` +** `HostSwitchProfileData.transportZoneIds`:关联的 transport zone UUID 列表,建立反向缓存 `transportZoneUuid → Set` + +[NOTE] +OpenAPI 中 `HostSwitchProfileData` 同时有 `type`(枚举:`dpdk | kernel`)和 `switchType`(字符串描述,如 `"OVS"`)两个字段。 +推导 `vSwitchType` 使用的是 `type` 枚举字段。 + +根据 `HostSwitchProfileData.type` 和 Cloud `HostVO.uuid` 创建 `SdnControllerHostRefVO`,`type` 与 `vSwitchType` 的映射规则: + +[cols="2,2"] +|=== +|ZNS HostSwitchProfileData.type |Cloud SdnControllerHostRefVO.vSwitchType + +|`dpdk` +|`OvsDpdk` + +|`kernel` +|`OvsKernel` + +|其它未知值,或者 profile 查询失败 +|`ZNS` + +|=== + +==== 5. 同步 Transport Zones + +调用 `GET /zns/api/v1/fabric/transport-zones` 获取 transport zone 列表,持久化到 `ZnsTransportZoneVO`。主要字段如下: + +[cols="2,3,2"] +|=== +|字段 |来源 |说明 + +|`uuid` +|transport zone UUID(转换为 Cloud 格式) +|主键 + +|`isDefault` +|每种 type 的第一个 transport zone 设为 true +|是否为默认 transport zone + +|`name` +|transport zone 返回字段 +|transport zone 名称 + +|`description` +|transport zone 返回字段 +|描述信息 + +|`type` +|transport zone 返回字段 +|类型,典型值为 `vlan` 或 `overlay` + +|`physicalNetwork` +|transport zone 返回字段 +|物理网络标识 + +|`status` +|transport zone 返回字段 +|当前状态 + +|`tags` +|transport zone 返回字段 +|标签信息 + +|`znsSdnControllerUuid` +|当前 `ZnsSdnControllerVO.uuid` +|外键,关联到所属 ZNS SDN Controller + +|=== + +[NOTE] +`isDefault` 的设置规则:每种 type(vlan、overlay)的第一个 transport zone 自动设为默认。重连时保留已有的默认设置。 + +==== 6. 同步 Segments 并创建 L2/L3 + +根据 computerManagerUuid 从 ZNS 获取 segments 列表(带 cms 过滤)。 + +对每个 segment: + +* 根据 segment 信息创建 L2Network、L3Network +* 根据 segment.ipam 信息创建 IpRange +* 通过 PATCH 将 Cloud L2 UUID 回写到 ZNS segment 的 cms.cms_resource_uuid 中 + +ZNS `segment.transport_type` 和 ZStack L2/L3 的映射关系如下: + +[cols="1,2,2,2"] +|=== +|ZNS字段 |条件 |Cloud L2Network.type |Cloud L3Network.type + +|`transport_type` +|`overlay` +|`L2GeneveNetwork` +|`L3ZnsNetwork` + +|`transport_type` +|`vlan` 且 `virtual_network_id > 0` +|`L2VlanNetwork` +|`L3ZnsNetwork` + +|`transport_type` +|`vlan` 且 `virtual_network_id == 0` +|`L2NoVlanNetwork` +|`L3ZnsNetwork` + +|=== + +补充说明: + +* `L2Network.vSwitchType` 固定写入 `ZNS` +* `L2Network.physicalInterface` 固定写入空字符串 +* `L2Network.virtualNetworkId` 取 `segment.virtual_network_id`(Geneve/VLAN) +* `L3Network.category` 当前初始化逻辑固定为 `Private` + +根据 `segment.transport_zone_uuid` 查询步骤 4 缓存的 `transportZoneUuid → Set` 映射,为每个关联的 cluster 创建一条 `L2NetworkClusterRefVO`,建立 ZNS segment 和 Cloud cluster 的映射关系。 + +`L2NetworkClusterRefVO` 的映射关系如下: + +[cols="2,3,2"] +|=== +|字段 |来源 |说明 + +|`l2NetworkUuid` +|当前 segment 创建出的 `L2NetworkVO.uuid` +|当前 L2 网络 UUID + +|`clusterUuid` +|`HostData.clusterId`(经 transport zone 反向缓存查找) +|`segment.transport_zone_uuid` → 缓存 → `Set` + +|`l2ProviderType` +|常量 `ZNS` +|固定值 + +|=== + +=== 重连SDN控制器 + +重连(`APIReconnectSdnControllerMsg`)与创建的核心区别: + +* **创建**:ZNS 是数据源,Cloud 单向从 ZNS 读取资源,并在 Cloud 侧新建 L2Network / L3Network / IpRange / `SdnControllerHostRefVO` / `L2NetworkClusterRefVO` 等对象。 +* **重连**:Cloud 数据库是基准,*不新建* Cloud 资源;仅对 `SdnControllerHostRefVO` 做 upsert,并将 ZNS 侧的 segment / segment port 状态对齐到 Cloud 侧。 + +==== 1. 刷新 SdnControllerHostRefVO(upsert) + +重连仍需重新扫描 host(包括 compute collections → discovered nodes → derive vSwitchType),以处理新加入的主机或 vSwitchType 发生变化的主机。 +映射关系与创建时完全相同(`HostData.managementIp` → `HostVO`,`HostSwitchProfileData.type` → `vSwitchType`), +区别仅在于写库操作改为 upsert: + +[cols="3,2"] +|=== +|情况 |操作 + +|`SdnControllerHostRefVO` 不存在(新主机) +|INSERT 新记录 + +|已存在但 `vSwitchType` 或 `vtepIp` 发生变化 +|UPDATE + +|已存在且字段未变 +|跳过 + +|=== + +[NOTE] +创建时只做 INSERT;重连时使用 upsert,可处理主机上下线及 vSwitchType 变更的情况。 + +==== 2. 刷新 Transport Zones + +调用 `GET /zns/api/v1/fabric/transport-zones` 重新拉取 transport zone 列表,对 `ZnsTransportZoneVO` 做 upsert,同时删除 ZNS 侧已不存在的旧记录。重连时保留已有的 `isDefault` 设置。 + +==== 3. Segment 协调(以 Cloud 为基准) + +重连以 Cloud 数据库中所有 `vSwitchType = ZNS` 的 `L2NetworkVO` 为基准,与 ZNS 侧属于本 Computer Manager 的 segment 做三路对比。 +ZNS 侧 segment 通过 cms 元数据中的 `cms_resource_uuid` 与 Cloud L2 关联。 + +[cols="2,2,3"] +|=== +|Cloud 侧 L2NetworkVO |ZNS 侧 segment |操作 + +|不存在(`cms_resource_uuid` 指向已删除 L2,或 segment 无 `cms_resource_uuid`) +|存在 +|调用 `DELETE /zns/api/v1/segments` 删除孤儿 segment + +|存在 +|不存在 +|调用 `POST /zns/api/v1/segments` 在 ZNS 新建 segment,参数来自 Cloud L2/L3/IpRange 信息 + +|存在 +|存在但参数不一致(名称、描述、CIDR 等) +|调用 `PATCH /zns/api/v1/segments/{uuid}` 更新 + +|存在 +|存在且参数一致 +|无操作(仅同步 systemTag 确保 segment UUID 映射正确) + +|=== + +[NOTE] +重连 *不会* 根据 ZNS segment 在 Cloud 侧创建新的 L2Network / L3Network / IpRange / `L2NetworkClusterRefVO`。 +如果 ZNS 存在但 Cloud 不存在,视为孤儿 segment 并删除(与创建阶段的单向导入方向相反)。 + +==== 4. Segment Port 协调 + +完成 segment 协调后,对每个已与 Cloud L2 匹配的 segment,逐一协调其 port。 +Port 通过 cms 元数据中的 `cms_resource_uuid` 与 Cloud `VmNicVO` 关联。 + +[cols="2,2,3"] +|=== +|Cloud 侧 VmNicVO |ZNS 侧 segment port |操作 + +|存在 +|不存在 +|调用 `POST /zns/api/v1/segments/{uuid}/ports` 补建 port + +|不存在 +|存在 +|调用 `DELETE /zns/api/v1/segments/{uuid}/ports` 删除孤儿 port + +|两侧均存在 +|(当前实现不做参数比对更新,仅同步 systemTag) +|无操作 + +|=== + +=== 心跳探活(Ping) + +Cloud 定期发送 `SdnControllerPingMsg`,通过调用 `GET /zns/api/v1/fabric/compute-managers/{uuid}` 验证 Computer Manager 连接是否正常。 + +* 验证成功 → 控制器保持 Connected 状态 +* 验证失败 → 控制器状态变为 Disconnected + +=== 删除SDN控制器 + +删除 ZNS SDN Controller 时的清理流程: + +. 根据 computerManagerUuid 从 ZNS 查询属于本 Controller 的所有 segments +. 批量调用 `DELETE /zns/api/v1/segments` 删除这些 segments(force = true),连带删除 port +. 删除 Cloud 本地的 `ZnsTransportZoneVO` 记录 +. 调用 `DELETE /zns/api/v1/fabric/compute-managers/{uuid}` 删除 Computer Manager + +[NOTE] +删除 segments 和 compute manager 过程中如果 ZNS 调用失败,仅打印告警日志,不阻断删除流程。 +Cloud 侧的 L2Network、L3Network、IpRange、VmNic 等资源由 `SdnControllerVO` 级联删除机制清理。 + +== L2Network + +=== 基础信息 + +`L2NetworkVO` 的重要字段: + +* `type`:L2NetworkType.types,有:L2NoVlanNetwork、L2VlanNetwork、VxlanNetworkPool、VxlanNetwork、TfL2Network、HardwareVxlanNetworkPool、HardwareVxlanNetwork +* `vSwitchType`:VSwitchType.types,有:LinuxBridge、TfL2Network、MacVlan、OvnDpdk、OvsDpdk、OvsKernel、ZNS +* `virtualNetworkId`:vlanId 或 vxlanID +* `physicalInterface`:物理网卡名称 + +ZNS L2Network 的类型定义: + +[cols="1,2"] +|=== +|属性 |值 + +|type +|L2NoVlanNetwork、L2VlanNetwork、L2GeneveNetwork(新增类型,类似于L2VlanNetwork) + +|vSwitchType +|ZNS(固定值,不区分 kernel 和 dpdk) + +|physicalInterface +|空字符串 + +|virtualNetworkId +|Vlan Id 或 Geneve Id + +|=== + +=== 创建 L2Network + +创建 ZNS 二层网络时,Cloud 根据 L2 类型从已缓存的 `ZnsTransportZoneVO` 中选择默认 transport zone(`isDefault = true`): + +[cols="2,2,3"] +|=== +|Cloud L2 类型 |默认 transport zone.type |说明 + +|`L2VlanNetwork` +|`vlan` +|LAN 网络默认使用 vlan 类型的 transport zone + +|`L2NoVlanNetwork` +|`vlan` +|NoVlan 网络默认使用 vlan 类型的 transport zone + +|`L2GeneveNetwork` +|`overlay` +|Geneve 网络默认使用 overlay 类型的 transport zone + +|=== + +调用 `POST /zns/api/v1/segments` 创建 segment,请求体包含:name、description、transport_type(转为大写)、transport_zone_uuid、virtual_network_id、cms 信息。 + +创建成功后,将 ZNS 返回的 segment UUID 通过 systemTag 记录到 L2NetworkVO 上。 + +[NOTE] +创建 L2Network 时,Cloud 会自动将 `vSwitchType` 设为 `ZNS`,`physicalInterface` 设为空字符串。这通过 API 拦截器(`ZnsApiInterceptor`)在 `APICreateL2NetworkMsg` 处理前自动完成。 + +[NOTE] +ZNS 基于 OVN 实现,OVN 不能提供类似 Cloud 的 cluster 能力。 +因此 Cloud 创建的 L2Network 在 OVN 侧默认可在该 transport zone 覆盖的全部物理机上使用,而不是由 OVN 提供 cluster 级隔离。 + +[NOTE] +Cloud 侧会额外施加一层调度约束:只有二层网络实际加载了某个 cluster 后,该 cluster 中的物理机才允许用于创建虚拟机。 +也就是说,transport zone 决定的是底层网络可达范围,`L2NetworkClusterRefVO` 决定的是 Cloud 侧可调度范围。 + +=== 删除 L2Network + +调用 `DELETE /zns/api/v1/segments` 删除对应的 ZNS segment(force = true),并清理 systemTag。 +如果 ZNS 侧删除失败(例如 segment 已不存在),仅打印告警日志,不阻断 Cloud 侧的删除流程。 + +=== APIChangeL2NetworkVlanIdMsg + +* L2GeneveNetwork 类型不支持修改 VlanId,需要在 API 拦截器中拦截:如果 L2Network 的 type 为 L2GeneveNetwork,抛出 `ApiMessageInterceptionException` +* L2VlanNetwork、L2NoVlanNetwork 类型支持 + +=== APIAttachL2NetworkToClusterMsg / APIDetachL2NetworkFromClusterMsg + +当前实现为空操作(no-op)。ZNS 通过 transport zone 管理网络覆盖范围,attach/detach cluster 仅影响 Cloud 侧的调度约束。 + +== L3Network + +=== 基础信息 + +`L3NetworkVO` 的重要字段: + +* `type`:L3BasicNetwork、L3VpcNetwork +* `category`:Public、Private、System + +ZNS L3 的定义: + +[cols="1,2"] +|=== +|属性 |值/规则 + +|type +|L3ZnsNetwork + +|category(L2GeneveNetwork 类型) +|只能是 Private + +|category(L2NoVlanNetwork、L2VlanNetwork 类型) +|可以是 Public 或 Private + +|=== + +ZNS L3Network 不添加网络服务。 + +=== 创建/删除 IpRange + +创建 IpRange 时,Cloud 调用 `PATCH /zns/api/v1/segments/{uuid}` 将 `gateway_address` 设为 IpRange 的 `networkCidr`,同步 CIDR 信息到 ZNS。 + +删除最后一个 IpRange 时,Cloud 调用 `PATCH /zns/api/v1/segments/{uuid}` 将 `gateway_address` 设为空字符串,清除 ZNS 侧的 CIDR 信息。 + +[NOTE] +创建/删除 L3Network 本身不触发 ZNS API 调用,仅 IpRange 的变化才同步到 ZNS。 + +=== MTU 同步 + +当用户修改 L3Network MTU 时,如果该 L3 属于 ZNS 网络,Cloud 调用 `PATCH /zns/api/v1/segments/{uuid}` 将 `mtu` 字段同步到 ZNS segment。 + +== VmNic + +VmNicType 的值有:VNIC、VF、`dpdkvhostuserclient`。ZNS 可能是 dpdk 模式,也可能是 kernel 模式。在 UI 选择 ZNS 网络后,用户可以选择网卡类型:VNIC 或 `dpdkvhostuserclient`。 + +=== 虚拟机的物理机分配 + +创建虚拟机选择了 ZNS 网络时: + +* 默认网卡类型是 VNIC,需要选择到部署了 OvnKernel 的物理机 +* 如果选择了 `dpdkvhostuserclient`,需要选择到部署了 OvnDpdk 的物理机 + +=== 网卡创建过程 + +创建虚拟机或给虚拟机添加网卡时,会调用 `VmAllocateNicFlow` 创建网卡。 + +ZNS 网络创建过程: + +. 和现在逻辑一样分配网卡 mac、internalId、internalName、driverType +. 调用 ZNS 创建 segment port API(`POST /zns/api/v1/segments/{uuid}/ports`),请求体包含:name、mac、ip、vm_uuid、cms 信息。ZNS 返回分配的 IP 地址 +. ZNS L3 网络走 `enableIpAddressAllocation()` 为 false 的流程,Cloud 直接把 ZNS 返回的 IP 地址保存到 `UsedIpVO`,不走 Cloud 侧的 IP 分配流程 +. 根据获取的参数创建/更新 `VmNicVO`、`UsedIpVO` +. 将 ZNS 返回的 port UUID 通过 systemTag 记录到 VmNicVO 上 + +=== 网卡删除过程 + +* `VmReturnReleaseNicFlow`:在 `destroyVmWorkFlowElements` 中被调用,用于虚拟机销毁时释放网卡资源 +* `VmDetachNicFlow`:在云主机删除网卡时调用 + +两个 Flow 中都需要: + +. 调用 ZNS 删除 segment port API(`DELETE /zns/api/v1/segments/{uuid}/ports`) +. 清理 systemTag +. 删除 `VmNicVO`、`UsedIpVO` + +[NOTE] +如果 ZNS 侧删除失败,仅打印告警日志,不阻断 Cloud 侧的删除流程。 + +=== IP 变更(VmIpChanged) + +当虚拟机 IP 发生变化时(`SetVmStaticIp`、`ChangeVmIp` 等操作),Cloud 调用 `PATCH /zns/api/v1/segments/{uuid}/ports/{portUuid}` 更新 ZNS 侧的 port IP。 + +=== ChangeVmNicNetwork(换网操作) + +`APIChangeVmNicNetworkMsg` 涉及 detach 旧网络 + attach 新网络: + +* 不支持从 ZNS 变换成非 ZNS 网络,或从非 ZNS 变换成 ZNS 网络(API 拦截器阻断) +* 不支持在不同 ZNS 控制器之间换网(API 拦截器阻断) +* 从 ZNS 网络变换成同一控制器下的 ZNS 网络: +** `beforeUpdateNic`:记录旧 port 上下文(znsIp、segmentUuid、portUuid) +** `afterUpdateNic`:先删除旧 segment port,再在新 segment 上创建新 port + +=== DPDK 网卡的特殊处理 + +由于 libvirt 不能自动创建 `dpdkvhostuserclient` 类型的网卡,Cloud 需要在虚拟机启动前,在物理机上预先创建对应的 `dpdkvhostuserclient` 网卡。 +这个逻辑与 OVN DPDK 虚拟网卡一致。 + +=== FilterAttachableL3NetworkExtensionPoint + +获取虚拟机可挂载的 L3 网络列表时,ZNS 网络有额外的过滤规则: + +* 如果虚拟机尚未挂载 ZNS 网络:ZNS 类型的 L3 仅当虚拟机所在物理机被该 ZNS 控制器管理时才可挂载 +* 如果虚拟机已挂载 ZNS 网络:只允许挂载同一 ZNS 控制器下的 L3 或非 ZNS 的 L3 + +== API 拦截器 + +`ZnsApiInterceptor` 在以下场景进行拦截: + +* `APICreateL2NetworkMsg`:如果 systemTag 指定了 ZNS SDN 控制器,自动设置 `vSwitchType = ZNS`、`physicalInterface = ""` +* `APIChangeL2NetworkVlanIdMsg`:禁止修改 L2GeneveNetwork 的 VLAN ID +* `APIChangeVmNicNetworkMsg`:禁止 ZNS 与非 ZNS 网络之间换网,禁止不同 ZNS 控制器之间换网 +* `APISdnControllerAddHostMsg` / `RemoveHostMsg` / `ChangeHostMsg`:禁止对 ZNS 控制器手动管理主机(由 ZNS 自动管理) + +[[api-version]] +== API 版本兼容性 + +=== 问题背景 + +Cloud 和 ZNS 是独立部署的两个组件,可能出现版本不一致: + +* **场景A**:Cloud 升级了,ZNS 没升级 — Cloud 发出 ZNS 不认识的 API 格式 +* **场景B**:ZNS 升级了,Cloud 没升级 — Cloud 用旧格式调新 API,参数缺失或语义变化 +* **场景C**:运行中 ZNS 被升级/降级 — Cloud 缓存的版本信息过期 + +=== 设计思路 + +.... +1. 每个 API 独立版本 + 不是给 ZNS 一个整体版本号,而是给每个 API 独立的版本号。 + ZNS 一年 2-3 个版本,但不是每个 API 都会变。 + +2. ZNS 是自己能力的真相源 + 每个 API 通过 HTTP OPTIONS 方法声明自己支持哪些版本。 + ZNS 各 API 各管各的版本,不集中到一个大接口里。 + +3. Cloud 通过 HTTP Header 声明自己发送的版本 + 每次请求带 X-Api-Version header,ZNS 可据此选择处理逻辑。 +.... + +=== 版本模型 + +==== 每个 API 有独立版本 + +.... +API 路径 + 方法 Cloud 发送的版本 ZNS 支持的版本 +────────────────────────────────────── ──────────────── ─────────────── +POST /zns/api/v1/segments 1.1 [1.0, 1.1] +PATCH /zns/api/v1/segments/{uuid} 1.1 [1.0, 1.1, 1.2] +DELETE /zns/api/v1/segments 1.0 [1.0] +GET /zns/api/v1/segments 1.1 [1.0, 1.1] +POST /zns/api/v1/segments/{id}/ports 1.0 [1.0, 1.1] +PATCH /zns/api/v1/segments/{id}/ports 1.1 [1.0, 1.1] +DELETE /zns/api/v1/segments/{id}/ports 1.0 [1.0] +.... + +* *Cloud 侧*:每个 API 有一个确定的版本,表示"我发出去的请求是什么格式" +* *ZNS 侧*:每个 API 支持一组版本,表示"我能理解哪些格式" +* *兼容条件*:Cloud 发送的版本 ∈ ZNS 支持的版本集合 + +==== API 版本变更举例 + +.... +ZNS v1.0 发布: + PATCH /segments 支持 [1.0] + POST /ports 支持 [1.0] + +ZNS v1.1 发布(PATCH segment 新增 mtu 字段): + PATCH /segments 支持 [1.0, 1.1] ← 新增 1.1,但仍兼容 1.0 + POST /ports 支持 [1.0] ← 没变 + +ZNS v1.2 发布(PATCH segment 删除了某个旧字段): + PATCH /segments 支持 [1.1, 1.2] ← 不再支持 1.0 + POST /ports 支持 [1.0, 1.1] ← 新增 1.1 +.... + +=== 版本查询:HTTP OPTIONS 方法 + +每个 API 路径通过标准的 HTTP OPTIONS 方法返回自己支持的版本。 + +.... +Cloud ZNS + │ │ + │ OPTIONS /zns/api/v1/segments │ + │───────────────────────────────────────────────────►│ + │ │ + │ ◄── 204 No Content │ + │ Headers: │ + │ Allow: GET, POST, DELETE │ ← 标准头 + │ X-Api-Versions: POST=1.0,1.1; │ ← 自定义头 + │ DELETE=1.0; │ + │ GET=1.0,1.1 │ + │ │ + │ OPTIONS /zns/api/v1/segments/{uuid} │ + │───────────────────────────────────────────────────►│ + │ │ + │ ◄── 204 No Content │ + │ Headers: │ + │ Allow: GET, PATCH │ + │ X-Api-Versions: PATCH=1.0,1.1,1.2; │ + │ GET=1.0,1.1 │ + │ │ +.... + +`X-Api-Versions` Header 格式: +.... +X-Api-Versions: {METHOD1}={ver1},{ver2};{METHOD2}={ver1},{ver2} + +示例:POST=1.0,1.1;DELETE=1.0;GET=1.0,1.1 +.... + +=== 版本声明:X-Api-Version 请求头 + +Cloud 每次发送 API 请求时,通过 Header 声明自己发送的是哪个版本的格式: + +.... +Cloud ZNS + │ │ + │ PATCH /zns/api/v1/segments/{uuid} │ + │ Headers: │ + │ X-Api-Version: 1.1 ← 声明版本 │ + │ Content-Type: application/json │ + │ x-web-hook: ... │ + │ x-job-uuid: ... │ + │ Body: │ + │ { "name": "...", "mtu": 9000 } ← 1.1 格式 │ + │───────────────────────────────────────────────────►│ + │ │ + │ ZNS 收到请求: │ + │ 1. 检查 X-Api-Version = 1.1 │ + │ 2. PATCH segments 支持 1.1? → 用 1.1 处理逻辑 │ + │ 不支持? → 返回 400 │ + │ │ +.... + +==== ZNS 处理不支持的版本 + +.... +ZNS 返回: + HTTP 400 Bad Request + { + "error": "unsupported_api_version", + "message": "PATCH /segments does not support version 1.3", + "supported_versions": ["1.0", "1.1", "1.2"] + } +.... + +==== ZNS 处理没有版本 Header 的请求(兼容旧版 Cloud) + +没有 `X-Api-Version` header 时,ZNS 按该 API 支持的最低版本处理,确保旧版 Cloud 仍能正常使用。 + +=== 版本检查时机 + +.... +核心原则: + 不在每次 API 调用前询问 ZNS(会导致每次操作延迟翻倍) + 而是在 3 个时机批量拉取版本信息,缓存在内存中 + + ZNS 版本一年才变 2-3 次,变版本必然有运维动作(升级), + 升级后通常会触发 reconnect。Ping 间隔内版本突然变化的概率极低。 +.... + +[cols="2,3,3"] +|=== +|时机 |触发条件 |行为 + +|添加控制器(preInit) +|用户首次添加 ZNS 控制器到 Cloud +|对每个 API 路径发 OPTIONS → 检查兼容性 → 缓存。不兼容则拒绝添加 + +|重连控制器(reconnect) +|管理员手动触发重连,或网络恢复后自动重连 +|清除旧缓存 → 重新发 OPTIONS → 检查 → 缓存。不兼容则重连失败 + +|心跳探活(ping) +|Cloud 定时 Ping ZNS +|对每个 API 路径发 OPTIONS → 与缓存对比。版本变化 → 刷新缓存 → 重新检查。不兼容 → Ping 失败 → 标记 Disconnected + +|=== + +=== 双层防御 + +版本兼容性在两个层面保证: + +.... +第 1 层: Cloud 提前检查(发请求之前) + Cloud 在 preInit/reconnect/ping 时通过 OPTIONS 拉取版本 + 缓存到内存,每次发请求前本地查缓存 + 不兼容 → 不发请求,直接报错或降级 + 作用: 避免发出注定失败的请求 + +第 2 层: ZNS 兜底校验(收到请求时) + ZNS 收到请求后检查 X-Api-Version header + 不支持 → 返回 400 + supported_versions + 作用: 即使 Cloud 缓存过期,ZNS 也能拒绝不兼容的请求 +.... + +=== 不兼容时的处理策略 + +不同 API 不兼容时,处理方式不同: + +[cols="3,1,3"] +|=== +|API (路径 + 方法) |重要性 |不兼容时行为 + +|POST /segments(创建网络) +|关键 +|阻断: 拒绝添加/重连控制器 + +|DELETE /segments(删除网络) +|关键 +|阻断 + +|POST /segments/{id}/ports(创建端口) +|关键 +|阻断 + +|DELETE /segments/{id}/ports(删除端口) +|关键 +|阻断 + +|GET /segments(列表,对账用) +|关键 +|阻断 + +|GET /segments/{id}/ports(列表,对账用) +|关键 +|阻断 + +|PATCH /segments/{uuid}(更新 mtu 等) +|非关键 +|降级: 跳过 + 打印告警日志 + +|PATCH /segments/{id}/ports/{id}(更新端口) +|非关键 +|降级: 跳过 + 打印告警日志 + +|=== + +=== 兼容旧版 + +==== Cloud 遇到旧版 ZNS(不支持 OPTIONS 版本响应) + +如果 OPTIONS 返回 404 或 204 但没有 `X-Api-Versions` header,Cloud 假设所有 API 只支持 `["1.0"]`。 + +==== ZNS 遇到旧版 Cloud(不带 X-Api-Version header) + +没有 `X-Api-Version` header 时,ZNS 按最低支持版本处理。 + +=== 错误信息设计 + +==== Cloud 新,ZNS 旧 + +.... +ZNS controller [192.168.1.10] does not support the following APIs +required by this Cloud version: + - PATCH /segments: Cloud sends v1.1, ZNS supports [1.0] + - POST /ports: Cloud sends v1.1, ZNS supports [1.0] +Please upgrade ZNS to a compatible version. +.... + +==== ZNS 新,Cloud 旧 + +.... +This Cloud version is not compatible with ZNS [192.168.1.10]: + - PATCH /segments: Cloud sends v1.0, ZNS supports [1.1, 1.2] (v1.0 dropped) +Please upgrade Cloud to a compatible version. +.... + +==== 非关键 API 降级 + +.... +WARN: ZNS [192.168.1.10] does not support PATCH /segments v1.1 +(ZNS supports [1.0]). MTU sync will be skipped. +This does not affect core network operations. Upgrade ZNS to enable MTU sync. +.... + +=== 版本维护规范 + +==== 什么时候需要升版本 + +需要升版本: + +* 请求体新增必填字段 +* 请求体删除字段 +* 字段类型变化(string → int) +* 字段语义变化(单位从 MB 变成 KB) +* 响应体结构变化(影响 Cloud 解析) + +不需要升版本: + +* 请求体新增可选字段(ZNS 忽略未知字段即可) +* 纯内部实现变化,接口不变 +* 性能优化,接口不变 + +==== ZNS 的向后兼容策略 + +建议每个 API 至少兼容前 2 个版本。 + +.... +示例: PATCH /segments + v1.2 发布时 → supported: [1.0, 1.1, 1.2] (兼容 3 个版本) + v1.3 发布时 → supported: [1.1, 1.2, 1.3] (下线 1.0) + +这样可以给 Cloud 升级留出足够的时间窗口。 +.... + +=== 双方职责总结 + +==== ZNS 侧 + +. 每个 API 路径实现 OPTIONS 方法,返回 `Allow` + `X-Api-Versions` header +. 收到请求时检查 `X-Api-Version` header:支持则用对应版本处理;不支持则返回 400;缺失则按最低版本处理 +. 每次 API 格式变化时在该 API 的 OPTIONS 响应中新增版本号,保留旧版本兼容 +. 新增 API 时实现 OPTIONS,初始版本为 `1.0` + +==== Cloud 侧 + +. 维护 `CLOUD_API_VERSIONS` 表,记录当前发出的每个 API 的格式版本 +. 在 preInit、reconnect、ping 三个时机通过 OPTIONS 拉取版本并缓存 +. 每次发请求带上 `X-Api-Version` header +. 每次修改 API 请求体格式时同步更新版本号 +. 标记每个 API 的重要性(关键/非关键),决定不兼容时是阻断还是降级 diff --git a/docs/modules/network/pages/networkResource/networkResource.adoc b/docs/modules/network/pages/networkResource/networkResource.adoc index 9aa66ce7341..b0fbf015282 100644 --- a/docs/modules/network/pages/networkResource/networkResource.adoc +++ b/docs/modules/network/pages/networkResource/networkResource.adoc @@ -2,4 +2,5 @@ * xref:networkResource/L2Network.adoc[] * xref:networkResource/L3Network.adoc[] -* xref:networkResource/VpcRouter.adoc[] \ No newline at end of file +* xref:networkResource/VpcRouter.adoc[] +* xref:networkResource/ZnsIntegration.adoc[] diff --git a/header/src/main/java/org/zstack/header/network/l2/L2NetworkConstant.java b/header/src/main/java/org/zstack/header/network/l2/L2NetworkConstant.java index e72b0b91396..f0b2dd42194 100755 --- a/header/src/main/java/org/zstack/header/network/l2/L2NetworkConstant.java +++ b/header/src/main/java/org/zstack/header/network/l2/L2NetworkConstant.java @@ -22,6 +22,8 @@ public interface L2NetworkConstant { public static final String HARDWARE_VXLAN_NETWORK_TYPE = "HardwareVxlanNetwork"; public static final String L2_TF_NETWORK_TYPE = "TfL2Network"; @PythonClass + public static final String L2_GENEVE_NETWORK_TYPE = "L2GeneveNetwork"; + @PythonClass public static final String VXLAN_NETWORK_TYPE = "VxlanNetwork"; @PythonClass public static final String VXLAN_NETWORK_POOL_TYPE = "VxlanNetworkPool"; @@ -37,7 +39,11 @@ public interface L2NetworkConstant { @PythonClass public static final String VSWITCH_TYPE_OVN_DPDK = "OvnDpdk"; + @PythonClass + public static final String VSWITCH_TYPE_ZNS = "ZNS"; public static final String OVN_DPDK_VNIC_SRC_PATH = "/var/run/openvswitch/"; + public static final String ACCEL_TYPE_VDPA = "vDPA"; + public static final String ACCEL_TYPE_VHOST_USER_SPACE = "dpdkvhostuserclient"; public static final String DETACH_L2NETWORK_CODE = "l2Network.detach"; diff --git a/header/src/main/java/org/zstack/header/network/l3/AfterSetL3NetworkMtuExtensionPoint.java b/header/src/main/java/org/zstack/header/network/l3/AfterSetL3NetworkMtuExtensionPoint.java new file mode 100644 index 00000000000..c884c697b00 --- /dev/null +++ b/header/src/main/java/org/zstack/header/network/l3/AfterSetL3NetworkMtuExtensionPoint.java @@ -0,0 +1,7 @@ +package org.zstack.header.network.l3; + +import org.zstack.header.core.Completion; + +public interface AfterSetL3NetworkMtuExtensionPoint { + void afterSetL3NetworkMtu(L3NetworkInventory l3, int mtu, Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkInventory.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkInventory.java index f1662f6421f..b40295ea289 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkInventory.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkInventory.java @@ -423,6 +423,9 @@ public boolean enableIpAddressAllocation() { } if (!getType().equals(L3NetworkConstant.L3_BASIC_NETWORK_TYPE)) { + if (L3NetworkType.hasType(getType())) { + return L3NetworkType.valueOf(getType()).isIpAddressAllocationEnabled(); + } return true; } diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkType.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkType.java index b60fbb45969..e759a88b57c 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkType.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkType.java @@ -6,6 +6,7 @@ public class L3NetworkType { private static Map types = Collections.synchronizedMap(new HashMap()); private final String typeName; private boolean exposed = true; + private boolean ipAddressAllocationEnabled = true; public L3NetworkType(String typeName) { this.typeName = typeName; @@ -25,6 +26,14 @@ public void setExposed(boolean exposed) { this.exposed = exposed; } + public boolean isIpAddressAllocationEnabled() { + return ipAddressAllocationEnabled; + } + + public void setIpAddressAllocationEnabled(boolean ipAddressAllocationEnabled) { + this.ipAddressAllocationEnabled = ipAddressAllocationEnabled; + } + public static boolean hasType(String typeName) { return types.containsKey(typeName); } diff --git a/header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java b/header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java index 0df379851dd..0ea342bd7a5 100755 --- a/header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java +++ b/header/src/main/java/org/zstack/header/network/l3/L3NetworkVO.java @@ -115,6 +115,9 @@ public boolean enableIpAddressAllocation() { } if (!getType().equals(L3NetworkConstant.L3_BASIC_NETWORK_TYPE)) { + if (L3NetworkType.hasType(getType())) { + return L3NetworkType.valueOf(getType()).isIpAddressAllocationEnabled(); + } return true; } diff --git a/header/src/main/java/org/zstack/header/vm/AfterAllocateSdnNicExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/AfterAllocateSdnNicExtensionPoint.java new file mode 100644 index 00000000000..97606fddcb4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/AfterAllocateSdnNicExtensionPoint.java @@ -0,0 +1,49 @@ +package org.zstack.header.vm; + +import org.zstack.header.core.Completion; + +import java.util.List; + +/** + * Extension point called after VmNicVO is persisted and non-SDN NIC IPs have + * been allocated (i.e. after VmAllocateNicIpFlow), but before the VM is + * instantiated on the hypervisor. + * + * Implementations should: + * 1. Filter NICs that belong to SDN-managed L2 networks (via VSwitchType). + * 2. Send REST API calls to the SDN controller to create ports (e.g. + * OVN logical switch ports, ZNS segment ports). + * 3. For controllers that return IP addresses (e.g. ZNS), write the + * returned IPs back into UsedIpVO and VmNicVO. + * + * If the implementation fails, VmAllocateSdnNicFlow will trigger a rollback + * via {@link #rollbackSdnNic}. + */ +public interface AfterAllocateSdnNicExtensionPoint { + /** + * Create SDN ports for the given NICs. + * + * @param spec the VM instance spec + * @param nics all NICs from spec.getDestNics() — implementation filters SDN NICs internally + * @param completion success/fail callback + */ + void afterAllocateSdnNic(VmInstanceSpec spec, List nics, Completion completion); + + /** + * Rollback: remove SDN ports and clean up any IPs allocated by the SDN controller. + * + * @param spec the VM instance spec + * @param nics all NICs from spec.getDestNics() + * @param completion success/fail callback (best-effort — failures should be logged but not block rollback) + */ + void rollbackSdnNic(VmInstanceSpec spec, List nics, Completion completion); + + /** + * Release SDN ports for NICs being detached or destroyed. + * Used by VmDetachNicFlow and VmReturnReleaseNicFlow. + * + * @param nics NICs to release (implementation filters SDN NICs internally) + * @param completion success/fail callback (best-effort) + */ + void releaseSdnNics(List nics, Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/vm/AfterAllocateVmNicIpExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/AfterAllocateVmNicIpExtensionPoint.java new file mode 100644 index 00000000000..0afba727a42 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/AfterAllocateVmNicIpExtensionPoint.java @@ -0,0 +1,19 @@ +package org.zstack.header.vm; + +import org.zstack.header.core.Completion; + +/** + * Extension point called after IP address(es) have been successfully allocated + * and flushed to the database for VmNics in VmAllocateNicIpFlow. + * + * At the time this fires: + * - VmNicVO rows exist in the database (created by VmAllocateNicFlow) + * - UsedIpVO rows are committed (allocated by VmAllocateNicIpFlow) + * - spec.getDestNics() contains up-to-date NIC inventories with IP info + * + * If the implementation fails, the flow chain rolls back: + * VmAllocateNicIpFlow.rollback (returns IPs) → VmAllocateNicFlow.rollback (deletes NICs). + */ +public interface AfterAllocateVmNicIpExtensionPoint { + void afterAllocateVmNicIp(VmInstanceSpec spec, Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java new file mode 100644 index 00000000000..102114111ca --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/AfterReleaseVmNicExtensionPoint.java @@ -0,0 +1,12 @@ +package org.zstack.header.vm; + +import org.zstack.header.core.Completion; + +/** + * Extension point called after a VmNic has been deleted from the database. + * Implementations perform post-deletion cleanup (e.g., deleting SDN segment ports). + * Cloud DB deletion must succeed before this extension point is invoked. + */ +public interface AfterReleaseVmNicExtensionPoint { + void afterReleaseVmNic(VmNicInventory nic, Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/vm/BeforeAllocateVmNicExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/BeforeAllocateVmNicExtensionPoint.java new file mode 100644 index 00000000000..f354a9d2521 --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/BeforeAllocateVmNicExtensionPoint.java @@ -0,0 +1,14 @@ +package org.zstack.header.vm; + +import org.zstack.header.core.Completion; + +/** + * Extension point called after a VmNic is persisted but before it is fully configured + * in VmAllocateNicFlow. The VmNicVO (and its ResourceVO) already exists in the database, + * so implementations may safely create SystemTags referencing the NIC UUID. + * If the implementation fails, the NIC will be cleaned up during flow rollback. + * Use case: create SDN segment ports and save port-UUID system tags for the NIC. + */ +public interface BeforeAllocateVmNicExtensionPoint { + void beforeAllocateVmNic(VmNicInventory nic, VmInstanceSpec spec, Completion completion); +} diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceNicFactory.java b/header/src/main/java/org/zstack/header/vm/VmInstanceNicFactory.java index 44ce4a8831e..690c30cf46d 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceNicFactory.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceNicFactory.java @@ -1,5 +1,6 @@ package org.zstack.header.vm; +import org.zstack.header.core.Completion; import org.zstack.header.network.l2.VSwitchType; import org.zstack.header.network.l3.UsedIpInventory; diff --git a/header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java b/header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java deleted file mode 100644 index 964a41e8861..00000000000 --- a/header/src/main/java/org/zstack/header/vm/VmOvsNicConstant.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.zstack.header.vm; - -import org.zstack.header.configuration.PythonClass; - -@PythonClass -public class VmOvsNicConstant { - public static final String ACCEL_TYPE_VDPA = "vDPA"; - public static final String ACCEL_TYPE_VHOST_USER_SPACE = "dpdkvhostuserclient"; -} diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java b/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java index 6f0b796e5b8..79578eb5686 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java @@ -80,7 +80,9 @@ private void validate(final APIAttachL2NetworkToClusterMsg msg) { /* current ovs only support vlan, vxlan*/ L2NetworkVO l2 = dbf.findByUuid(msg.getL2NetworkUuid(), L2NetworkVO.class); - if (!StringUtils.isEmpty(l2.getPhysicalInterface())) { + // ZNS L2 networks are managed by SDN controller, physicalInterface is irrelevant + if (!L2NetworkConstant.VSWITCH_TYPE_ZNS.equals(l2.getvSwitchType()) + && !StringUtils.isEmpty(l2.getPhysicalInterface())) { /* find l2 network with same physical interface, but different vswitch Type */ List otherL2s = Q.New(L2NetworkVO.class).select(L2NetworkVO_.uuid) .eq(L2NetworkVO_.physicalInterface, l2.getPhysicalInterface()) @@ -127,26 +129,45 @@ private void validate(APICreateL2NetworkMsg msg) { private void validate(APIChangeL2NetworkVlanIdMsg msg) { L2NetworkVO l2 = dbf.findByUuid(msg.getL2NetworkUuid(), L2NetworkVO.class); - l2.getAttachedClusterRefs().forEach(ref -> { - if (Q.New(HostVO.class).eq(HostVO_.clusterUuid, ref.getClusterUuid()) - .notEq(HostVO_.status, HostStatus.Connected).isExists()) { - throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_L2_10013, "cannot change vlan for l2Network[uuid:%s]" + - " because there are hosts status in Connecting or Disconnected", l2.getUuid())); - } - if (!Q.New(ClusterVO.class).eq(ClusterVO_.uuid, ref.getClusterUuid()) - .eq(ClusterVO_.hypervisorType, L2NetworkConstant.KVM_HYPERVISOR_TYPE).isExists()) { - throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_L2_10014, "cannot change vlan for l2Network[uuid:%s]" + - " because it only supports an L2Network that is exclusively attached to a kvm cluster", l2.getUuid())); - } - }); + if (!L2NetworkConstant.VSWITCH_TYPE_ZNS.equals(l2.getvSwitchType())) { + l2.getAttachedClusterRefs().forEach(ref -> { + if (Q.New(HostVO.class).eq(HostVO_.clusterUuid, ref.getClusterUuid()) + .notEq(HostVO_.status, HostStatus.Connected).isExists()) { + throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_L2_10013, "cannot change vlan for l2Network[uuid:%s]" + + " because there are hosts status in Connecting or Disconnected", l2.getUuid())); + } + if (!Q.New(ClusterVO.class).eq(ClusterVO_.uuid, ref.getClusterUuid()) + .eq(ClusterVO_.hypervisorType, L2NetworkConstant.KVM_HYPERVISOR_TYPE).isExists()) { + throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_L2_10014, "cannot change vlan for l2Network[uuid:%s]" + + " because it only supports an L2Network that is exclusively attached to a kvm cluster", l2.getUuid())); + } + }); + } // pvlan isolated not support change vlan if (l2.getIsolated()) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10015, "cannot change vlan for l2Network[uuid:%s]" + " because this l2Network is isolated", l2.getUuid())); } + String targetType = StringUtils.trimToNull(msg.getType()); + msg.setType(targetType); + // When type is not specified (or blank), default to the current network type. + if (targetType == null) { + targetType = l2.getType(); + msg.setType(targetType); + } + + boolean targetIsVlan = L2NetworkConstant.L2_VLAN_NETWORK_TYPE.equals(targetType); + boolean targetIsNoVlan = L2NetworkConstant.L2_NO_VLAN_NETWORK_TYPE.equals(targetType); + boolean targetIsGeneve = L2NetworkConstant.L2_GENEVE_NETWORK_TYPE.equals(targetType); + boolean targetIsVxlan = L2NetworkConstant.VXLAN_NETWORK_TYPE.equals(targetType); + if (!targetIsVlan && !targetIsNoVlan && !targetIsGeneve && !targetIsVxlan) { + throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10021, + "unsupported l2Network type[%s] for ChangeL2NetworkVlanId", targetType)); + } + String sdnControllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID .getTokenByResourceUuid(msg.getL2NetworkUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); - if (msg.getType().equals(L2NetworkConstant.L2_VLAN_NETWORK_TYPE)) { + if (targetIsVlan) { if (msg.getVlan() == null) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10016, "vlan is required for " + "ChangeL2NetworkVlanId with type[%s]", msg.getType())); @@ -157,7 +178,9 @@ private void validate(APIChangeL2NetworkVlanIdMsg msg) { List attachedClusters = l2.getAttachedClusterRefs().stream() .map(L2NetworkClusterRefVO::getClusterUuid).collect(Collectors.toList()); List l2s; - if (sdnControllerUuid == null) { + if (attachedClusters.isEmpty()) { + l2s = java.util.Collections.emptyList(); + } else if (sdnControllerUuid == null) { l2s = SQL.New("select l2" + " from L2NetworkVO l2, L2NetworkClusterRefVO ref" + " where l2.uuid = ref.l2NetworkUuid" + @@ -190,7 +213,7 @@ private void validate(APIChangeL2NetworkVlanIdMsg msg) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10018, "There has been a l2Network attached to cluster with virtual network id[%s] and physical interface[%s]. Failed to change L2 network[uuid:%s]", msg.getVlan(), l2.getPhysicalInterface(), l2.getUuid())); } - } else if (msg.getType().equals(L2NetworkConstant.L2_NO_VLAN_NETWORK_TYPE)) { + } else if (targetIsNoVlan) { if (msg.getVlan() != null) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10019, "vlan is not allowed for " + "ChangeL2NetworkVlanId with type[%s]", msg.getType())); @@ -198,7 +221,9 @@ private void validate(APIChangeL2NetworkVlanIdMsg msg) { List attachedClusters = l2.getAttachedClusterRefs().stream() .map(L2NetworkClusterRefVO::getClusterUuid).collect(Collectors.toList()); List l2s; - if (sdnControllerUuid != null) { + if (attachedClusters.isEmpty()) { + l2s = java.util.Collections.emptyList(); + } else if (sdnControllerUuid != null) { l2s = SQL.New("select l2" + " from L2NetworkVO l2, L2NetworkClusterRefVO ref, SystemTagVO tag" + " where l2.uuid = ref.l2NetworkUuid" + @@ -227,6 +252,15 @@ private void validate(APIChangeL2NetworkVlanIdMsg msg) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10020, "There has been a l2Network attached to cluster that has physical interface[%s]. Failed to change l2Network[uuid:%s]", l2.getPhysicalInterface(), l2.getUuid())); } + } else if (targetIsGeneve) { + if (msg.getVlan() == null) { + throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10016, "vni is required for " + + "ChangeL2NetworkVlanId with type[%s]", msg.getType())); + } + if (msg.getVlan() < 1 || msg.getVlan() > 16777215) { + throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10017, "invalid vni[%d] for " + + "ChangeL2NetworkVlanId, must be between 1 and 16777215", msg.getVlan())); + } } } } diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java b/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java index fa864bafaf2..b1eecae4d5e 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkExtensionPointEmitter.java @@ -53,6 +53,20 @@ public void run(L2NetworkDeleteExtensionPoint arg) { }); } + public void beforeUpdate(final L2NetworkInventory inv) { + for (L2NetworkUpdateExtensionPoint ext : updateExtensions) { + try { + ext.beforeChangeL2NetworkVlanId(inv); + } catch (RuntimeException e) { + // propagate validation failures and other runtime exceptions immediately + throw e; + } catch (Exception e) { + logger.warn(String.format("unhandled exception in L2NetworkUpdateExtensionPoint.beforeChangeL2NetworkVlanId of %s", + ext.getClass().getCanonicalName()), e); + } + } + } + public void afterUpdate(final L2NetworkInventory inv) { CollectionUtils.safeForEach(updateExtensions, arg -> arg.afterChangeL2NetworkVlanId(inv)); } diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java b/network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java index a612b3847c5..0136649f4bf 100644 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkHostHelper.java @@ -15,7 +15,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import static java.util.Arrays.asList; @@ -87,6 +86,10 @@ public L2NetworkHostRefInventory getL2NetworkHostRef(String l2NetworkUuid, Strin } public static Set getHostsByL2NetworkAttachedCluster(L2NetworkInventory l2NetworkInventory) { + if (l2NetworkInventory.getAttachedClusterUuids() == null || l2NetworkInventory.getAttachedClusterUuids().isEmpty()) { + return new HashSet<>(); + } + return new HashSet<>(Q.New(HostVO.class) .in(HostVO_.clusterUuid, l2NetworkInventory.getAttachedClusterUuids()) .notIn(HostVO_.state,asList(HostState.PreMaintenance, HostState.Maintenance)) diff --git a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java index 821a76ad462..ec9a7a5be0b 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java +++ b/network/src/main/java/org/zstack/network/l2/L2NoVlanNetwork.java @@ -443,6 +443,7 @@ public String getSyncSignature() { @Override public void run(SyncTaskChain chain) { + extpEmitter.beforeUpdate(getSelfInventory()); changeL2NetworkVlanId(msg, new Completion(chain) { @Override public void success() { @@ -968,6 +969,10 @@ protected void scripts() { } L2NetworkVO tl2 = Q.New(L2NetworkVO.class).eq(L2NetworkVO_.uuid, msg.getL2NetworkUuid()).find(); + // ZNS L2NoVlan segments are uniquely identified by SDN controller, not by physicalInterface + if (L2NetworkConstant.VSWITCH_TYPE_ZNS.equals(tl2.getvSwitchType())) { + return; + } for (L2NetworkVO l2 : l2s) { if (l2.getPhysicalInterface().equals(tl2.getPhysicalInterface())) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10006, "There has been a l2Network[uuid:%s, name:%s] attached to cluster[uuid:%s] that has physical interface[%s]. Failed to attach l2Network[uuid:%s]", @@ -980,8 +985,10 @@ protected void scripts() { l2s = SQL.New("select l2" + " from L2VlanNetworkVO l2, L2NetworkClusterRefVO ref" + " where l2.uuid = ref.l2NetworkUuid" + - " and ref.clusterUuid = :clusterUuid") - .param("clusterUuid", msg.getClusterUuid()).list(); + " and ref.clusterUuid = :clusterUuid" + + " and l2.vSwitchType != :znsType") + .param("clusterUuid", msg.getClusterUuid()) + .param("znsType", L2NetworkConstant.VSWITCH_TYPE_ZNS).list(); } else { l2s = SQL.New("select l2" + " from L2VlanNetworkVO l2, L2NetworkClusterRefVO ref, SystemTagVO tag" + diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java index 384a5d2c1df..77b7ca81495 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java @@ -184,6 +184,41 @@ public void run(MessageReply reply) { } }); } + }).then(new NoRollbackFlow() { + @Override + public void run(FlowTrigger trigger, Map data) { + List exts = + pluginRgty.getExtensionList(AfterSetL3NetworkMtuExtensionPoint.class); + if (exts.isEmpty()) { + trigger.next(); + return; + } + + L3NetworkInventory l3Inv = L3NetworkInventory.valueOf(dbf.findByUuid(msg.getL3NetworkUuid(), L3NetworkVO.class)); + new While<>(exts).each((ext, wcompl) -> { + ext.afterSetL3NetworkMtu(l3Inv, msg.getMtu(), new Completion(wcompl) { + @Override + public void success() { + wcompl.done(); + } + + @Override + public void fail(ErrorCode errorCode) { + wcompl.addError(errorCode); + wcompl.allDone(); + } + }); + }).run(new WhileDoneCompletion(trigger) { + @Override + public void done(ErrorCodeList errorCodeList) { + if (errorCodeList.getCauses().isEmpty()) { + trigger.next(); + } else { + trigger.fail(errorCodeList.getCauses().get(0)); + } + } + }); + } }).done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index a8a1378288b..6ed711cb251 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -1242,6 +1242,14 @@ public static class NicTO extends BaseVirtualDeviceTO { private Boolean isolated; + // bridge sub-type: null for Linux bridge, "openvswitch" for OVS bridge + // generates in libvirt XML + private String bridgePortType; + + // OVS external_ids:iface-id, used by SDN controller to identify the port + // generates + private String interfaceId; + public List getIps() { return ips; } @@ -1434,6 +1442,22 @@ public void setL2NetworkUuid(String l2NetworkUuid) { this.l2NetworkUuid = l2NetworkUuid; } + public String getBridgePortType() { + return bridgePortType; + } + + public void setBridgePortType(String bridgePortType) { + this.bridgePortType = bridgePortType; + } + + public String getInterfaceId() { + return interfaceId; + } + + public void setInterfaceId(String interfaceId) { + this.interfaceId = interfaceId; + } + public static NicTO fromVmNicInventory(VmNicInventory nic) { KVMAgentCommands.NicTO to = new KVMAgentCommands.NicTO(); to.setMac(nic.getMac()); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java index 93fd688935b..e8e30a6711f 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMApiInterceptor.java @@ -111,6 +111,11 @@ private void validate(APIAttachL2NetworkToClusterMsg msg) { return; } + // ZNS L2 networks don't create vlan devices on host, skip length check + if (L2NetworkConstant.VSWITCH_TYPE_ZNS.equals(l2.getvSwitchType())) { + return; + } + if (NetworkUtils.generateVlanDeviceName(l2.getPhysicalInterface(), l2.getVlan()).length() > L2NetworkConstant.LINUX_IF_NAME_MAX_SIZE) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_KVM_10139, "cannot create vlan-device on %s because it's too long" diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index a245757517d..3872012b6ce 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -4179,6 +4179,16 @@ private NicTO completeNicInfo(VmNicInventory nic) { KVMCompleteNicInformationExtensionPoint extp = factory.getCompleteNicInfoExtension(L2NetworkType.valueOf(l2inv.getType())); NicTO to = extp.completeNicInformation(l2inv, l3Inv, nic); + if (L2NetworkConstant.VSWITCH_TYPE_ZNS.equals(l2inv.getvSwitchType())) { + to.setBridgeName("br-int"); + to.setBridgePortType("openvswitch"); + String ifaceId = VmSystemTags.IFACE_ID.getTokenByResourceUuid( + nic.getUuid(), VmSystemTags.IFACE_ID_TOKEN); + if (ifaceId != null) { + to.setInterfaceId(ifaceId); + } + } + if (to.getUseVirtio() == null) { to.setUseVirtio(VmSystemTags.VIRTIO.hasTag(nic.getVmInstanceUuid())); to.setIps(getCleanTrafficIp(nic)); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java index acb129fe6dc..86a32d715c9 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java @@ -47,6 +47,7 @@ import org.zstack.header.network.l2.L2NetworkType; import org.zstack.header.network.l2.L2NetworkVO; import org.zstack.header.network.l2.L2NetworkVO_; + import org.zstack.header.rest.RESTFacade; import org.zstack.header.rest.SyncHttpCallHandler; import org.zstack.header.tag.FormTagExtensionPoint; @@ -371,7 +372,7 @@ protected void populateExtensions() { public KVMCompleteNicInformationExtensionPoint getCompleteNicInfoExtension(L2NetworkType type) { KVMCompleteNicInformationExtensionPoint extp = completeNicInfoExtensions.get(type); if (extp == null) { - throw new IllegalArgumentException(String.format("unble to fine KVMCompleteNicInformationExtensionPoint supporting L2NetworkType[%s]", type)); + throw new IllegalArgumentException(String.format("unable to find KVMCompleteNicInformationExtensionPoint supporting L2NetworkType[%s]", type)); } return extp; } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java index 1ed462ecfcd..3ec9dd0780e 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java @@ -236,7 +236,7 @@ public NicTO completeNicInformation(L2NetworkInventory l2Network, L3NetworkInven to.setBridgeName(makeBridgeName(l2Network.getUuid())); to.setPhysicalInterface(l2Network.getPhysicalInterface()); to.setMtu(new MtuGetter().getMtu(l3Network.getUuid())); - if (l2Network.getvSwitchType().equals(L2NetworkConstant.VSWITCH_TYPE_OVN_DPDK)) { + if (L2NetworkConstant.ACCEL_TYPE_VHOST_USER_SPACE.equals(nic.getType())) { to.setSrcPath(L2NetworkConstant.OVN_DPDK_VNIC_SRC_PATH + nic.getInternalName()); } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java index 74e700dd9f6..e486fddff98 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java @@ -249,7 +249,7 @@ public NicTO completeNicInformation(L2NetworkInventory l2Network, L3NetworkInven to.setMetaData(String.valueOf(vlanId)); to.setMtu(new MtuGetter().getMtu(l3Network.getUuid())); to.setVlanId(String.valueOf(vlanId)); - if (l2Network.getvSwitchType().equals(L2NetworkConstant.VSWITCH_TYPE_OVN_DPDK)) { + if (L2NetworkConstant.ACCEL_TYPE_VHOST_USER_SPACE.equals(nic.getType())) { to.setSrcPath(L2NetworkConstant.OVN_DPDK_VNIC_SRC_PATH + nic.getInternalName()); } diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java index f1d1f11829b..5fb9ea1c368 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java @@ -903,7 +903,7 @@ public void fail(ErrorCode errorCode) { @Override public void run(FlowTrigger trigger, Map data) { sdnPingTracker.untrack(msg.getSdnControllerUuid()); - dbf.removeByPrimaryKey(msg.getSdnControllerUuid(), SdnControllerVO.class); + controller.deleteSdnControllerDb(self); trigger.next(); } }).done(new FlowDoneHandler(completion) { diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerFactory.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerFactory.java index 6c25dcaddb9..95604b61646 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerFactory.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerFactory.java @@ -10,8 +10,6 @@ public interface SdnControllerFactory { SdnControllerType getVendorType(); - SdnControllerVO persistSdnController(SdnControllerVO vo); - SdnController getSdnController(SdnControllerVO vo); default SdnController getSdnController(String l2NetworkUuid) {return null;}; diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java index 2368fbb95ae..e879d436472 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerManagerImpl.java @@ -47,10 +47,10 @@ import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; public class SdnControllerManagerImpl extends AbstractService implements SdnControllerManager, - L2NetworkCreateExtensionPoint, L2NetworkDeleteExtensionPoint, InstantiateResourceOnAttachingNicExtensionPoint, - PreVmInstantiateResourceExtensionPoint, VmReleaseResourceExtensionPoint, - ReleaseNetworkServiceOnDetachingNicExtensionPoint, SecurityGroupGetSdnBackendExtensionPoint, - AfterAddIpRangeExtensionPoint, IpRangeDeletionExtensionPoint, GetSdnControllerExtensionPoint { + L2NetworkCreateExtensionPoint, L2NetworkDeleteExtensionPoint, + SecurityGroupGetSdnBackendExtensionPoint, + AfterAddIpRangeExtensionPoint, IpRangeDeletionExtensionPoint, GetSdnControllerExtensionPoint, + AfterAllocateSdnNicExtensionPoint { private static final CLogger logger = Utils.getLogger(SdnControllerManagerImpl.class); private static final Logger log = LoggerFactory.getLogger(SdnControllerManagerImpl.class); @@ -270,8 +270,13 @@ private void handle(APIAddSdnControllerMsg msg) { @Override public void run(MessageReply reply) { if (reply.isSuccess()) { - tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), SdnControllerVO.class.getSimpleName()); - event.setInventory(SdnControllerInventory.valueOf(dbf.findByUuid(vo.getUuid(), SdnControllerVO.class))); + try { + tagMgr.createTagsFromAPICreateMessage(msg, vo.getUuid(), SdnControllerVO.class.getSimpleName()); + event.setInventory(SdnControllerInventory.valueOf(dbf.findByUuid(vo.getUuid(), SdnControllerVO.class))); + } catch (Exception e) { + logger.warn(String.format("failed to load SdnControllerVO[uuid:%s] after init: %s", + vo.getUuid(), e.getMessage()), e); + } } else { event.setError(reply.getError()); } @@ -454,267 +459,13 @@ public void done(ErrorCodeList errorCodeList) { }); } - @Override - public void releaseVmResource(VmInstanceSpec spec, Completion completion) { - if (VmInstanceConstant.VmOperation.DetachNic != spec.getCurrentVmOperation() && - VmInstanceConstant.VmOperation.Destroy != spec.getCurrentVmOperation()) { - completion.success(); - return; - } - - if (spec.getL3Networks() == null || spec.getL3Networks().isEmpty()) { - completion.success(); - return; - } - - // we run into this situation when VM nics are all detached and the - // VM is being rebooted - if (spec.getDestNics().isEmpty()) { - completion.success(); - return; - } - - Map> nicMaps = new HashMap<>(); - for (VmNicInventory nic : spec.getDestNics()) { - L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class); - if (l3Vo == null) { - continue; - } - - L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class); - if (l2VO == null) { - continue; - } - - VSwitchType vSwitchType = VSwitchType.valueOf(l2VO.getvSwitchType()); - if (vSwitchType.getSdnControllerType() == null) { - continue; - } - - String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( - l2VO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); - if (controllerUuid == null) { - completion.fail(operr(ORG_ZSTACK_SDNCONTROLLER_10005, "sdn l2 network[uuid:%s] is not attached controller", l2VO.getUuid())); - return; - } - - nicMaps.computeIfAbsent(controllerUuid, k -> new ArrayList<>()).add(nic); - } - - if (nicMaps.isEmpty()) { - completion.success(); - return; - } - - removeLogicalPort(nicMaps, completion); - } - - @Override - public void instantiateResourceOnAttachingNic(VmInstanceSpec spec, L3NetworkInventory l3, Completion completion) { - L2NetworkVO l2NetworkVO = dbf.findByUuid(l3.getL2NetworkUuid(), L2NetworkVO.class); - VSwitchType vSwitchType = VSwitchType.valueOf(l2NetworkVO.getvSwitchType()); - if (vSwitchType.getSdnControllerType() == null) { - completion.success(); - return; - } - - String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( - l2NetworkVO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); - if (controllerUuid == null) { - completion.fail(operr(ORG_ZSTACK_SDNCONTROLLER_10006, "sdn l2 network[uuid:%s] is not attached controller", l2NetworkVO.getUuid())); - return; - } - - Map> nicMaps = new HashMap<>(); - List nics = new ArrayList<>(); - nics.add(spec.getDestNics().get(0)); - nicMaps.put(controllerUuid, nics); - sdnAddVmNics(nicMaps, completion); - } - - @Override - public void releaseResourceOnAttachingNic(VmInstanceSpec spec, L3NetworkInventory l3, NoErrorCompletion completion) { - L2NetworkVO l2NetworkVO = dbf.findByUuid(l3.getL2NetworkUuid(), L2NetworkVO.class); - VSwitchType vSwitchType = VSwitchType.valueOf(l2NetworkVO.getvSwitchType()); - if (vSwitchType.getSdnControllerType() == null) { - completion.done(); - return; - } - - String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( - l2NetworkVO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); - if (controllerUuid == null) { - logger.warn(String.format("sdn l2 network[uuid:%s] is not attached controller", l2NetworkVO.getUuid())); - completion.done(); - return; - } - - Map> nicMaps = new HashMap<>(); - List nics = new ArrayList<>(); - nics.add(spec.getDestNics().get(0)); - nicMaps.put(controllerUuid, nics); - - removeLogicalPort(nicMaps, new Completion(completion) { - @Override - public void success() { - completion.done(); - } - - @Override - public void fail(ErrorCode errorCode) { - logger.info(String.format("failed to remove logical port for vm[uuid:%s] nic[internalName:%s], because: %s", - spec.getVmInventory().getUuid(), spec.getDestNics().get(0).getInternalName(), errorCode.getDetails())); - completion.done(); - } - }); - } - - @Override - public void releaseResourceOnDetachingNic(VmInstanceSpec spec, VmNicInventory nic, NoErrorCompletion completion) { - L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class); - L2NetworkVO l2NetworkVO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class); - VSwitchType vSwitchType = VSwitchType.valueOf(l2NetworkVO.getvSwitchType()); - if (vSwitchType.getSdnControllerType() == null) { - completion.done(); - return; - } - - String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( - l2NetworkVO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); - if (controllerUuid == null) { - logger.warn(String.format("sdn l2 network[uuid:%s] is not attached controller", l2NetworkVO.getUuid())); - completion.done(); - return; - } - - Map> nicMaps = new HashMap<>(); - List nics = new ArrayList<>(); - nics.add(spec.getDestNics().get(0)); - nicMaps.put(controllerUuid, nics); - - removeLogicalPort(nicMaps, new Completion(completion) { - @Override - public void success() { - completion.done(); - } - - @Override - public void fail(ErrorCode errorCode) { - logger.info(String.format("failed to remove logical port for vm[uuid:%s] nic[internalName:%s], because: %s", - spec.getVmInventory().getUuid(), spec.getDestNics().get(0).getInternalName(), errorCode.getDetails())); - completion.done(); - } - }); - } - - @Override - public void preBeforeInstantiateVmResource(VmInstanceSpec spec) throws VmInstantiateResourceException { - - } - - @Override - public void preInstantiateVmResource(VmInstanceSpec spec, Completion completion) { - if (spec.getL3Networks() == null || spec.getL3Networks().isEmpty()) { - completion.success(); - return; - } - - // we run into this situation when VM nics are all detached and the - // VM is being rebooted - if (spec.getDestNics().isEmpty()) { - completion.success(); - return; - } - - Map> nicMaps = new HashMap<>(); - for (VmNicInventory nic : spec.getDestNics()) { - L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class); - if (l3Vo == null) { - continue; - } - - L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class); - if (l2VO == null) { - continue; - } - - VSwitchType vSwitchType = VSwitchType.valueOf(l2VO.getvSwitchType()); - if (vSwitchType.getSdnControllerType() ==null) { - continue; - } - - String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( - l2VO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); - if (controllerUuid == null) { - completion.fail(operr(ORG_ZSTACK_SDNCONTROLLER_10007, "sdn l2 network[uuid:%s] is not attached controller", l2VO.getUuid())); - return; - } - - nicMaps.computeIfAbsent(controllerUuid, k -> new ArrayList<>()).add(nic); - } - - if (nicMaps.isEmpty()) { - completion.success(); - return; - } - - sdnAddVmNics(nicMaps, completion); - } - - @Override - public void preReleaseVmResource(VmInstanceSpec spec, Completion completion) { - // create/start/reboot vm failed, code will go here VmInstantiateResourcePreFlow.rollack() - // vm change image failed, - if (VmInstanceConstant.VmOperation.NewCreate != spec.getCurrentVmOperation()) { - completion.success(); - return; - } - - if (spec.getL3Networks() == null || spec.getL3Networks().isEmpty()) { - completion.success(); - return; - } - - // we run into this situation when VM nics are all detached and the - // VM is being rebooted - if (spec.getDestNics().isEmpty()) { - completion.success(); - return; - } - - Map> nicMaps = new HashMap<>(); - for (VmNicInventory nic : spec.getDestNics()) { - L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class); - if (l3Vo == null) { - continue; - } - - L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class); - if (l2VO == null) { - continue; - } - - VSwitchType vSwitchType = VSwitchType.valueOf(l2VO.getvSwitchType()); - if (vSwitchType.getSdnControllerType() ==null) { - continue; - } - - String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( - l2VO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); - if (controllerUuid == null) { - completion.fail(operr(ORG_ZSTACK_SDNCONTROLLER_10008, "sdn l2 network[uuid:%s] is not attached controller", l2VO.getUuid())); - return; - } - - nicMaps.computeIfAbsent(controllerUuid, k -> new ArrayList<>()).add(nic); - } - - if (nicMaps.isEmpty()) { - completion.success(); - return; - } - - removeLogicalPort(nicMaps, completion); + /** + * Returns true if the L2 network should be skipped for SDN port management: + * it has no SDN controller type configured on its VSwitchType. + */ + private boolean shouldSkipSdnForNic(L2NetworkVO l2VO) { + VSwitchType vSwitchType = VSwitchType.valueOf(l2VO.getvSwitchType()); + return vSwitchType.getSdnControllerType() == null; } @Override @@ -899,4 +650,113 @@ private SdnControllerVO getSdnControllerVO(L3NetworkInventory l3Network) { } return dbf.findByUuid(sdnControllerUuid, SdnControllerVO.class); } + + @Override + public void afterAllocateSdnNic(VmInstanceSpec spec, List nics, Completion completion) { + if (nics == null || nics.isEmpty()) { + completion.success(); + return; + } + + Map> nicMaps = new HashMap<>(); + for (VmNicInventory nic : nics) { + L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class); + if (l3Vo == null) { + continue; + } + + L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class); + if (l2VO == null || shouldSkipSdnForNic(l2VO)) { + continue; + } + + String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( + l2VO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); + if (controllerUuid == null) { + completion.fail(operr(ORG_ZSTACK_SDNCONTROLLER_10006, "sdn l2 network[uuid:%s] has not attached controller", l2VO.getUuid())); + return; + } + + nicMaps.computeIfAbsent(controllerUuid, k -> new ArrayList<>()).add(nic); + } + + if (nicMaps.isEmpty()) { + completion.success(); + return; + } + + sdnAddVmNics(nicMaps, completion); + } + + @Override + public void rollbackSdnNic(VmInstanceSpec spec, List nics, Completion completion) { + if (nics == null || nics.isEmpty()) { + completion.success(); + return; + } + + Map> nicMaps = new HashMap<>(); + for (VmNicInventory nic : nics) { + L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class); + if (l3Vo == null) { + continue; + } + + L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class); + if (l2VO == null || shouldSkipSdnForNic(l2VO)) { + continue; + } + + String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( + l2VO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); + if (controllerUuid == null) { + continue; + } + + nicMaps.computeIfAbsent(controllerUuid, k -> new ArrayList<>()).add(nic); + } + + if (nicMaps.isEmpty()) { + completion.success(); + return; + } + + removeLogicalPort(nicMaps, completion); + } + + @Override + public void releaseSdnNics(List nics, Completion completion) { + if (nics == null || nics.isEmpty()) { + completion.success(); + return; + } + + Map> nicMaps = new HashMap<>(); + for (VmNicInventory nic : nics) { + L3NetworkVO l3Vo = dbf.findByUuid(nic.getL3NetworkUuid(), L3NetworkVO.class); + if (l3Vo == null) { + continue; + } + + L2NetworkVO l2VO = dbf.findByUuid(l3Vo.getL2NetworkUuid(), L2NetworkVO.class); + if (l2VO == null || shouldSkipSdnForNic(l2VO)) { + continue; + } + + String controllerUuid = L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID.getTokenByResourceUuid( + l2VO.getUuid(), L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN); + if (controllerUuid == null) { + continue; + } + + nicMaps.computeIfAbsent(controllerUuid, k -> new ArrayList<>()).add(nic); + } + + if (nicMaps.isEmpty()) { + completion.success(); + return; + } + + removeLogicalPort(nicMaps, completion); + } } diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/h3cVcfc/H3cVcfcSdnControllerFactory.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/h3cVcfc/H3cVcfcSdnControllerFactory.java index 2d297ca518c..7647a086824 100644 --- a/plugin/sdnController/src/main/java/org/zstack/sdnController/h3cVcfc/H3cVcfcSdnControllerFactory.java +++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/h3cVcfc/H3cVcfcSdnControllerFactory.java @@ -20,12 +20,6 @@ public SdnControllerType getVendorType() { return sdnControllerType; } - @Override - public SdnControllerVO persistSdnController(SdnControllerVO vo) { - vo = dbf.persistAndRefresh(vo); - return vo; - } - @Override public SdnController getSdnController(SdnControllerVO vo) { diff --git a/plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/SugonSdnControllerFactory.java b/plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/SugonSdnControllerFactory.java index b277a336c3d..fad3e58f886 100644 --- a/plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/SugonSdnControllerFactory.java +++ b/plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/SugonSdnControllerFactory.java @@ -21,11 +21,6 @@ public SdnControllerType getVendorType() { return sdnControllerType; } - @Override - public SdnControllerVO persistSdnController(SdnControllerVO vo) { - vo = dbf.persistAndRefresh(vo); - return vo; - } @Override public SdnController getSdnController(SdnControllerVO vo) { diff --git a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanNetworkCheckerImpl.java b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanNetworkCheckerImpl.java index d3a46ed8d78..9f9e9848714 100644 --- a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanNetworkCheckerImpl.java +++ b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanNetworkCheckerImpl.java @@ -38,7 +38,7 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti } private void validate(APIChangeL2NetworkVlanIdMsg msg) { - if (!msg.getType().equals(VxlanNetworkConstant.VXLAN_NETWORK_TYPE)){ + if (!VxlanNetworkConstant.VXLAN_NETWORK_TYPE.equals(msg.getType())){ return; } if (!NetworkUtils.isValidVni(msg.getVlan())) { diff --git a/rest/src/main/resources/scripts/RestDocumentationGenerator.groovy b/rest/src/main/resources/scripts/RestDocumentationGenerator.groovy index c11a35e40a7..7f4df6504a9 100755 --- a/rest/src/main/resources/scripts/RestDocumentationGenerator.groovy +++ b/rest/src/main/resources/scripts/RestDocumentationGenerator.groovy @@ -787,10 +787,13 @@ class RestDocumentationGenerator implements DocumentGenerator { return globalConfigMarkDown } - Boolean isConsistent(GlobalConfigMarkDown md, GlobalConfig globalConfig) { - if (md == null || globalConfig == null) { - return false - } + List isConsistent(GlobalConfigMarkDown md, GlobalConfig globalConfig) { + if (md == null) { + return ["GlobalConfigMarkDown is null"] + } + if (globalConfig == null) { + return ["GlobalConfig is null"] + } String mdPath = PathUtil.join(PathUtil.join(Paths.get("../doc").toAbsolutePath().normalize().toString(), "globalconfig"), md.globalConfig.category, md.globalConfig.name) + ".md" @@ -798,70 +801,77 @@ class RestDocumentationGenerator implements DocumentGenerator { initializer.bindResources.get(globalConfig.getIdentity()).each { classes.add(it.getName()) } List newClasses = classes.sort() String validatorString = initializer.validatorMap.get(globalConfig.getIdentity()) - Boolean flag = true - if (md.globalConfig.name != globalConfig.name) { - logger.info("name of ${mdPath} is not latest") - flag = false - } - if (md.globalConfig.defaultValue != globalConfig.defaultValue) { - logger.info("defaultValue of ${mdPath} is not latest") - flag = false - } - if (StringUtils.trimToEmpty(md.globalConfig.description) != StringUtils.trimToEmpty(globalConfig.description)) { - logger.info("desc of ${mdPath} is not latest") - flag = false - } - if (md.globalConfig.type != globalConfig.type) { - if (globalConfig.type != null) { - logger.info("type of ${mdPath} is not latest") - flag = false - } - } - if (md.globalConfig.category != globalConfig.category) { - logger.info("category of ${mdPath} is not latest") - flag = false - } - List oldClasses = md.globalConfig.resources.sort() - if (oldClasses != newClasses) { - logger.info("classes of ${mdPath} is not latest") - flag = false - } + List mismatches = [] + if (md.globalConfig.name != globalConfig.name) { + logger.info("name of ${mdPath} is not latest") + mismatches.add("name mismatch in ${mdPath}: markdown='${md.globalConfig.name}', current='${globalConfig.name}'") + } + if (md.globalConfig.defaultValue != globalConfig.defaultValue) { + logger.info("defaultValue of ${mdPath} is not latest") + mismatches.add("defaultValue mismatch in ${mdPath}: markdown='${md.globalConfig.defaultValue}', current='${globalConfig.defaultValue}'") + } + if (StringUtils.trimToEmpty(md.globalConfig.description) != StringUtils.trimToEmpty(globalConfig.description)) { + logger.info("desc of ${mdPath} is not latest") + mismatches.add("description mismatch in ${mdPath}: markdown='${StringUtils.trimToEmpty(md.globalConfig.description)}', current='${StringUtils.trimToEmpty(globalConfig.description)}'") + } + if (md.globalConfig.type != globalConfig.type) { + if (globalConfig.type != null) { + logger.info("type of ${mdPath} is not latest") + mismatches.add("type mismatch in ${mdPath}: markdown='${md.globalConfig.type}', current='${globalConfig.type}'") + } + } + if (md.globalConfig.category != globalConfig.category) { + logger.info("category of ${mdPath} is not latest") + mismatches.add("category mismatch in ${mdPath}: markdown='${md.globalConfig.category}', current='${globalConfig.category}'") + } + List oldClasses = md.globalConfig.resources.sort() + if (oldClasses != newClasses) { + logger.info("classes of ${mdPath} is not latest") + mismatches.add("resources mismatch in ${mdPath}: markdown='${oldClasses}', current='${newClasses}'") + } if (md.globalConfig.valueRange != (validatorString)) { boolean useBooleanValidator = (globalConfig.type == "java.lang.Boolean" && md.globalConfig.valueRange == "{true, false}") - if (validatorString != null || !useBooleanValidator) { - logger.info("valueRange of ${mdPath} is not latest") - logger.info("valueRange = ${md.globalConfig.valueRange} validatorString = ${validatorString}") - flag = false - } - } - return flag - } + if (validatorString != null || !useBooleanValidator) { + logger.info("valueRange of ${mdPath} is not latest") + logger.info("valueRange = ${md.globalConfig.valueRange} validatorString = ${validatorString}") + mismatches.add("valueRange mismatch in ${mdPath}: markdown='${md.globalConfig.valueRange}', current='${validatorString}'") + } + } + return mismatches + } void checkMD(String mdPath, GlobalConfig globalConfig) { String result = ShellUtils.runAndReturn( "grep '${PLACEHOLDER}' ${mdPath}").stdout.replaceAll("\n", "") - if (!result.empty) { - throw new CloudRuntimeException("Placeholders are detected in ${mdPath}, please replace them by content.") - } - GlobalConfigMarkDown markDown = getExistGlobalConfigMarkDown(mdPath) - if (markDown.desc_CN.isEmpty() - || markDown.name_CN.isEmpty() - || markDown.valueRangeRemark.isEmpty() - || markDown.defaultValueRemark.isEmpty() - || markDown.resourcesGranularitiesRemark.isEmpty() - || markDown.additionalRemark.isEmpty() - || markDown.backgroundInformation.isEmpty() - || markDown.isUIExposed.isEmpty() - || markDown.isCLIExposed.isEmpty() - ) { - throw new CloudRuntimeException("The necessary information of ${mdPath} is missing, please complete the information before submission.") - } - if (!isConsistent(markDown, globalConfig)) { - throw new CloudRuntimeException("${mdPath} is not match with its definition, please use Repair mode to correct it.") - } - } + if (!result.empty) { + throw new CloudRuntimeException("Placeholders detected in ${mdPath}; please replace them with actual content.") + } + GlobalConfigMarkDown markDown = getExistGlobalConfigMarkDown(mdPath) + List missingFields = [] + if (markDown.desc_CN.isEmpty()) missingFields.add("desc_CN") + if (markDown.name_CN.isEmpty()) missingFields.add("name_CN") + if (markDown.valueRangeRemark.isEmpty()) missingFields.add("valueRangeRemark") + if (markDown.defaultValueRemark.isEmpty()) missingFields.add("defaultValueRemark") + if (markDown.resourcesGranularitiesRemark.isEmpty()) missingFields.add("resourcesGranularitiesRemark") + if (markDown.additionalRemark.isEmpty()) missingFields.add("additionalRemark") + if (markDown.backgroundInformation.isEmpty()) missingFields.add("backgroundInformation") + if (markDown.isUIExposed.isEmpty()) missingFields.add("isUIExposed") + if (markDown.isCLIExposed.isEmpty()) missingFields.add("isCLIExposed") + List inconsistencies = isConsistent(markDown, globalConfig) + if (!missingFields.isEmpty() || !inconsistencies.isEmpty()) { + StringBuilder sb = new StringBuilder("Validation failed for ${mdPath}:\n") + if (!missingFields.isEmpty()) { + sb.append("Missing required fields: ${missingFields}\n") + } + if (!inconsistencies.isEmpty()) { + sb.append("Inconsistent fields:\n") + inconsistencies.each { sb.append("- ${it}\n") } + } + throw new CloudRuntimeException(sb.toString()) + } + } class ElaborationMarkDown { private def table = ["|编号|描述|原因|操作建议|更多|"] @@ -2815,23 +2825,32 @@ ${additionalRemark} return System.getProperty("ignoreError") != null } - void testGlobalConfigTemplateAndMarkDown() { - Map allConfigs = initializer.configs - allConfigs.each { - String newPath = - PathUtil.join(PathUtil.join(Paths.get("../doc").toAbsolutePath().normalize().toString(), - "globalconfig"), it.value.category, it.value.name) + DEPRECATED + ".md" - if (new File(newPath).exists()) { + void testGlobalConfigTemplateAndMarkDown() { + Map allConfigs = initializer.configs + List allErrors = [] + allConfigs.each { + String newPath = + PathUtil.join(PathUtil.join(Paths.get("../doc").toAbsolutePath().normalize().toString(), + "globalconfig"), it.value.category, it.value.name) + DEPRECATED + ".md" + if (new File(newPath).exists()) { return } - String mdPath = - PathUtil.join(PathUtil.join(Paths.get("../doc").toAbsolutePath().normalize().toString(), - "globalconfig"), it.value.category, it.value.name) + ".md" - File mdFile = new File(mdPath) - if (!mdFile.exists()) { - throw new CloudRuntimeException("Not found the document markdown of the global config ${it.value.name} , please generate it first.") - } - checkMD(mdPath, it.value) - } - } -} + String mdPath = + PathUtil.join(PathUtil.join(Paths.get("../doc").toAbsolutePath().normalize().toString(), + "globalconfig"), it.value.category, it.value.name) + ".md" + File mdFile = new File(mdPath) + if (!mdFile.exists()) { + allErrors.add("Global config markdown not found: ${mdPath}") + return + } + try { + checkMD(mdPath, it.value) + } catch (CloudRuntimeException e) { + allErrors.add(e.message) + } + } + if (!allErrors.isEmpty()) { + throw new CloudRuntimeException(allErrors.join("\n\n")) + } + } +} diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 614350ecd69..671562c5368 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -608,6 +608,9 @@ public class SourceClassMap { put("org.zstack.network.service.virtualrouter.VirtualRouterOfferingInventory", "org.zstack.sdk.VirtualRouterOfferingInventory"); put("org.zstack.network.service.virtualrouter.VirtualRouterSoftwareVersionInventory", "org.zstack.sdk.VirtualRouterSoftwareVersionInventory"); put("org.zstack.network.service.virtualrouter.VirtualRouterVmInventory", "org.zstack.sdk.VirtualRouterVmInventory"); + put("org.zstack.network.zns.L2GeneveNetworkInventory", "org.zstack.sdk.L2GeneveNetworkInventory"); + put("org.zstack.network.zns.ZnsControllerInventory", "org.zstack.sdk.ZnsControllerInventory"); + put("org.zstack.network.zns.ZnsTransportZoneInventory", "org.zstack.sdk.ZnsTransportZoneInventory"); put("org.zstack.observabilityServer.ObservabilityServerOfferingInventory", "org.zstack.sdk.ObservabilityServerOfferingInventory"); put("org.zstack.observabilityServer.ObservabilityServerVmInventory", "org.zstack.sdk.ObservabilityServerVmInventory"); put("org.zstack.observabilityServer.service.ObservabilityServerServiceDataInventory", "org.zstack.sdk.ObservabilityServerServiceDataInventory"); @@ -1185,6 +1188,7 @@ public class SourceClassMap { put("org.zstack.sdk.KvmCephIsoTO", "org.zstack.storage.ceph.primary.KvmCephIsoTO"); put("org.zstack.sdk.KvmHostHypervisorMetadataInventory", "org.zstack.kvm.hypervisor.datatype.KvmHostHypervisorMetadataInventory"); put("org.zstack.sdk.KvmHypervisorInfoInventory", "org.zstack.kvm.hypervisor.datatype.KvmHypervisorInfoInventory"); + put("org.zstack.sdk.L2GeneveNetworkInventory", "org.zstack.network.zns.L2GeneveNetworkInventory"); put("org.zstack.sdk.L2NetworkData", "org.zstack.header.network.l2.L2NetworkData"); put("org.zstack.sdk.L2NetworkInventory", "org.zstack.header.network.l2.L2NetworkInventory"); put("org.zstack.sdk.L2PortGroupNetworkInventory", "org.zstack.network.l2.virtualSwitch.header.L2PortGroupNetworkInventory"); @@ -1623,6 +1627,8 @@ public class SourceClassMap { put("org.zstack.sdk.ZdfsInventory", "org.zstack.header.zdfs.ZdfsInventory"); put("org.zstack.sdk.ZdfsService", "org.zstack.ai.message.ModelCenterServiceInventory$ZdfsService"); put("org.zstack.sdk.ZdfsStorageInventory", "org.zstack.header.zdfs.ZdfsStorageInventory"); + put("org.zstack.sdk.ZnsControllerInventory", "org.zstack.network.zns.ZnsControllerInventory"); + put("org.zstack.sdk.ZnsTransportZoneInventory", "org.zstack.network.zns.ZnsTransportZoneInventory"); put("org.zstack.sdk.ZoneInventory", "org.zstack.header.zone.ZoneInventory"); put("org.zstack.sdk.databasebackup.DatabaseBackupInventory", "org.zstack.header.storage.database.backup.DatabaseBackupInventory"); put("org.zstack.sdk.databasebackup.DatabaseBackupStorageRefInventory", "org.zstack.header.storage.database.backup.DatabaseBackupStorageRefInventory"); diff --git a/sdk/src/main/java/org/zstack/sdk/CreateL2GeneveNetworkAction.java b/sdk/src/main/java/org/zstack/sdk/CreateL2GeneveNetworkAction.java new file mode 100644 index 00000000000..e76ddab6ff8 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CreateL2GeneveNetworkAction.java @@ -0,0 +1,131 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class CreateL2GeneveNetworkAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.CreateL2NetworkResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, numberRange = {1L,16777214L}, noTrim = false) + public java.lang.Integer geneveId; + + @Param(required = true, maxLength = 255, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String name; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String description; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String zoneUuid; + + @Param(required = false, maxLength = 1024, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String physicalInterface; + + @Param(required = false) + public java.lang.String type; + + @Param(required = false, validValues = {"LinuxBridge","OvsDpdk","MacVlan","OvnDpdk"}, maxLength = 1024, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String vSwitchType = "LinuxBridge"; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.Boolean isolated = false; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = false, noTrim = false) + public java.lang.String pvlan; + + @Param(required = false) + public java.lang.String resourceUuid; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.util.List tagUuids; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.CreateL2NetworkResult value = res.getResult(org.zstack.sdk.CreateL2NetworkResult.class); + ret.value = value == null ? new org.zstack.sdk.CreateL2NetworkResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "POST"; + info.path = "/l2-networks/geneve"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "params"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/L2GeneveNetworkInventory.java b/sdk/src/main/java/org/zstack/sdk/L2GeneveNetworkInventory.java new file mode 100644 index 00000000000..bbf314e095c --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/L2GeneveNetworkInventory.java @@ -0,0 +1,15 @@ +package org.zstack.sdk; + + + +public class L2GeneveNetworkInventory extends org.zstack.sdk.L2NetworkInventory { + + public java.lang.Integer geneveId; + public void setGeneveId(java.lang.Integer geneveId) { + this.geneveId = geneveId; + } + public java.lang.Integer getGeneveId() { + return this.geneveId; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/NicTO.java b/sdk/src/main/java/org/zstack/sdk/NicTO.java index 6eecb1466a8..c1457f95d4b 100644 --- a/sdk/src/main/java/org/zstack/sdk/NicTO.java +++ b/sdk/src/main/java/org/zstack/sdk/NicTO.java @@ -197,4 +197,20 @@ public java.lang.Boolean getIsolated() { return this.isolated; } + public java.lang.String bridgePortType; + public void setBridgePortType(java.lang.String bridgePortType) { + this.bridgePortType = bridgePortType; + } + public java.lang.String getBridgePortType() { + return this.bridgePortType; + } + + public java.lang.String interfaceId; + public void setInterfaceId(java.lang.String interfaceId) { + this.interfaceId = interfaceId; + } + public java.lang.String getInterfaceId() { + return this.interfaceId; + } + } diff --git a/sdk/src/main/java/org/zstack/sdk/ZnsControllerInventory.java b/sdk/src/main/java/org/zstack/sdk/ZnsControllerInventory.java new file mode 100644 index 00000000000..2f207674572 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ZnsControllerInventory.java @@ -0,0 +1,15 @@ +package org.zstack.sdk; + + + +public class ZnsControllerInventory extends org.zstack.sdk.SdnControllerInventory { + + public java.util.List transportZones; + public void setTransportZones(java.util.List transportZones) { + this.transportZones = transportZones; + } + public java.util.List getTransportZones() { + return this.transportZones; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ZnsTransportZoneInventory.java b/sdk/src/main/java/org/zstack/sdk/ZnsTransportZoneInventory.java new file mode 100644 index 00000000000..d3026306804 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ZnsTransportZoneInventory.java @@ -0,0 +1,95 @@ +package org.zstack.sdk; + + + +public class ZnsTransportZoneInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String name; + public void setName(java.lang.String name) { + this.name = name; + } + public java.lang.String getName() { + return this.name; + } + + public java.lang.String description; + public void setDescription(java.lang.String description) { + this.description = description; + } + public java.lang.String getDescription() { + return this.description; + } + + public java.lang.String type; + public void setType(java.lang.String type) { + this.type = type; + } + public java.lang.String getType() { + return this.type; + } + + public java.lang.String physicalNetwork; + public void setPhysicalNetwork(java.lang.String physicalNetwork) { + this.physicalNetwork = physicalNetwork; + } + public java.lang.String getPhysicalNetwork() { + return this.physicalNetwork; + } + + public java.lang.String status; + public void setStatus(java.lang.String status) { + this.status = status; + } + public java.lang.String getStatus() { + return this.status; + } + + public boolean isDefault; + public void setIsDefault(boolean isDefault) { + this.isDefault = isDefault; + } + public boolean getIsDefault() { + return this.isDefault; + } + + public java.lang.String tags; + public void setTags(java.lang.String tags) { + this.tags = tags; + } + public java.lang.String getTags() { + return this.tags; + } + + public java.lang.String znsSdnControllerUuid; + public void setZnsSdnControllerUuid(java.lang.String znsSdnControllerUuid) { + this.znsSdnControllerUuid = znsSdnControllerUuid; + } + public java.lang.String getZnsSdnControllerUuid() { + return this.znsSdnControllerUuid; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index 8a8f2b91416..2c6715bc92c 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -962,8 +962,8 @@ abstract class ApiHelper { } - def addBareMetal2Gateway(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2GatewayAction.class) Closure c) { - def a = new org.zstack.sdk.AddBareMetal2GatewayAction() + def addBareMetal2DpuChassis(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2DpuChassisAction.class) Closure c) { + def a = new org.zstack.sdk.AddBareMetal2DpuChassisAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -989,8 +989,8 @@ abstract class ApiHelper { } - def addBareMetal2IpmiChassis(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2IpmiChassisAction.class) Closure c) { - def a = new org.zstack.sdk.AddBareMetal2IpmiChassisAction() + def addBareMetal2Gateway(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2GatewayAction.class) Closure c) { + def a = new org.zstack.sdk.AddBareMetal2GatewayAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -1016,26 +1016,26 @@ abstract class ApiHelper { } - def addBareMetal2DpuChassis(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2DpuChassisAction.class) Closure c) { - def a = new org.zstack.sdk.AddBareMetal2DpuChassisAction() + def addBareMetal2IpmiChassis(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AddBareMetal2IpmiChassisAction.class) Closure c) { + def a = new org.zstack.sdk.AddBareMetal2IpmiChassisAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -10061,6 +10061,33 @@ abstract class ApiHelper { } + def createL2GeneveNetwork(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateL2GeneveNetworkAction.class) Closure c) { + def a = new org.zstack.sdk.CreateL2GeneveNetworkAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def createL2HardwareVxlanNetwork(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateL2HardwareVxlanNetworkAction.class) Closure c) { def a = new org.zstack.sdk.CreateL2HardwareVxlanNetworkAction() a.sessionId = Test.currentEnvSpec?.session?.uuid diff --git a/testlib/src/main/java/org/zstack/testlib/SdnControllerSpec.groovy b/testlib/src/main/java/org/zstack/testlib/SdnControllerSpec.groovy index 25b24d7f412..cb05661c4f5 100644 --- a/testlib/src/main/java/org/zstack/testlib/SdnControllerSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/SdnControllerSpec.groovy @@ -1,8 +1,10 @@ package org.zstack.testlib +import org.springframework.http.HttpEntity import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.zstack.sdk.SdnControllerInventory +import org.zstack.utils.gson.JSONObjectUtil import org.zstack.sdnController.h3cVcfc.H3cVcfcCommands import org.zstack.sdnController.h3cVcfc.H3cVcfcCommands.LoginReply import org.zstack.sdnController.h3cVcfc.H3cVcfcCommands.LoginRsp @@ -15,7 +17,6 @@ import org.zstack.sdnController.h3cVcfc.H3cVcfcCommands.GetH3cTenantsRsp import org.zstack.sdnController.h3cVcfc.H3cVcfcCommands.H3cTenantStruct import org.zstack.sdnController.h3cVcfc.H3cVcfcCommands.GetH3cTeamLederIpReply import org.zstack.sdnController.h3cVcfc.H3cVcfcV2Commands -import org.springframework.http.HttpEntity import org.zstack.sugonSdnController.controller.SugonSdnControllerConstant import org.zstack.sugonSdnController.controller.api.ApiSerializer import org.zstack.sugonSdnController.controller.api.TfCommands @@ -58,9 +59,9 @@ class SdnControllerSpec extends Spec implements HasSession { } postCreate { - inventory = querySdnController { + inventory = JSONObjectUtil.rehashObject(querySdnController { conditions=["uuid=${inventory.uuid}".toString()] - }[0] + }[0], SdnControllerInventory.class) } return id(name, inventory.uuid) diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 0deb26d677d..20d2870ed5c 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -1015,6 +1015,7 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_NETWORK_L2_10019 = "ORG_ZSTACK_NETWORK_L2_10019"; public static final String ORG_ZSTACK_NETWORK_L2_10020 = "ORG_ZSTACK_NETWORK_L2_10020"; + public static final String ORG_ZSTACK_NETWORK_L2_10021 = "ORG_ZSTACK_NETWORK_L2_10021"; public static final String ORG_ZSTACK_CONSOLE_10000 = "ORG_ZSTACK_CONSOLE_10000"; @@ -11930,6 +11931,54 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_NETWORK_OVN_10084 = "ORG_ZSTACK_NETWORK_OVN_10084"; + public static final String ORG_ZSTACK_NETWORK_ZNS_10000 = "ORG_ZSTACK_NETWORK_ZNS_10000"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10001 = "ORG_ZSTACK_NETWORK_ZNS_10001"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10002 = "ORG_ZSTACK_NETWORK_ZNS_10002"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10003 = "ORG_ZSTACK_NETWORK_ZNS_10003"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10004 = "ORG_ZSTACK_NETWORK_ZNS_10004"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10005 = "ORG_ZSTACK_NETWORK_ZNS_10005"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10006 = "ORG_ZSTACK_NETWORK_ZNS_10006"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10007 = "ORG_ZSTACK_NETWORK_ZNS_10007"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10008 = "ORG_ZSTACK_NETWORK_ZNS_10008"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10009 = "ORG_ZSTACK_NETWORK_ZNS_10009"; + + // ZNS error-code semantic mapping: + // 10010 unsupported API for ZNS controller + // 10011 ZNS L2 only supports L3BasicNetwork + // 10012 duplicate ZNS L2NoVlan creation under same controller + // 10013 invalid ZNS L2 target type in change-vlan flow + // 10014 Geneve type can only change VNI, not L2 type + // 10015 cannot switch to Geneve / cannot move NIC across ZNS controllers + // 10016 duplicate Geneve VNI under same controller + // 10017 non-ZNS L2 cannot change to Geneve type + // 10018 ZNS non-Geneve L2 network cannot change type to L2GeneveNetwork + public static final String ORG_ZSTACK_NETWORK_ZNS_10010 = "ORG_ZSTACK_NETWORK_ZNS_10010"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10011 = "ORG_ZSTACK_NETWORK_ZNS_10011"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10012 = "ORG_ZSTACK_NETWORK_ZNS_10012"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10013 = "ORG_ZSTACK_NETWORK_ZNS_10013"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10014 = "ORG_ZSTACK_NETWORK_ZNS_10014"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10015 = "ORG_ZSTACK_NETWORK_ZNS_10015"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10016 = "ORG_ZSTACK_NETWORK_ZNS_10016"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10017 = "ORG_ZSTACK_NETWORK_ZNS_10017"; + + public static final String ORG_ZSTACK_NETWORK_ZNS_10018 = "ORG_ZSTACK_NETWORK_ZNS_10018"; + public static final String ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000 = "ORG_ZSTACK_PREMIUM_EXTERNALSERVICE_MARKETPLACE_10000"; public static final String ORG_ZSTACK_ALIYUN_NAS_STORAGE_PRIMARY_IMAGESTORE_10000 = "ORG_ZSTACK_ALIYUN_NAS_STORAGE_PRIMARY_IMAGESTORE_10000";