usedIps;
private String internalName;
private Integer deviceId;
@@ -223,6 +225,14 @@ public void setDriverType(String driverType) {
this.driverType = driverType;
}
+ public String getInterfaceId() {
+ return interfaceId;
+ }
+
+ public void setInterfaceId(String interfaceId) {
+ this.interfaceId = interfaceId;
+ }
+
public String getType() {
return type;
}
diff --git a/header/src/main/java/org/zstack/header/vm/VmNicLifecycleContext.java b/header/src/main/java/org/zstack/header/vm/VmNicLifecycleContext.java
new file mode 100644
index 00000000000..3300b17e33a
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/VmNicLifecycleContext.java
@@ -0,0 +1,60 @@
+package org.zstack.header.vm;
+
+import org.zstack.header.vm.VmInstanceConstant.VmOperation;
+
+import java.util.Objects;
+
+public class VmNicLifecycleContext {
+ private VmOperation operation;
+ private String vmUuid;
+ private String srcHostUuid;
+ private String destHostUuid;
+ private String lastHostUuid;
+
+ public VmOperation getOperation() {
+ return operation;
+ }
+
+ public void setOperation(VmOperation operation) {
+ this.operation = operation;
+ }
+
+ public String getVmUuid() {
+ return vmUuid;
+ }
+
+ public void setVmUuid(String vmUuid) {
+ this.vmUuid = vmUuid;
+ }
+
+ public String getSrcHostUuid() {
+ return srcHostUuid;
+ }
+
+ public void setSrcHostUuid(String srcHostUuid) {
+ this.srcHostUuid = srcHostUuid;
+ }
+
+ public String getDestHostUuid() {
+ return destHostUuid;
+ }
+
+ public void setDestHostUuid(String destHostUuid) {
+ this.destHostUuid = destHostUuid;
+ }
+
+ public String getLastHostUuid() {
+ return lastHostUuid;
+ }
+
+ public void setLastHostUuid(String lastHostUuid) {
+ this.lastHostUuid = lastHostUuid;
+ }
+
+ public boolean isStartWithChangedHost() {
+ return operation == VmOperation.Start
+ && lastHostUuid != null
+ && destHostUuid != null
+ && !Objects.equals(lastHostUuid, destHostUuid);
+ }
+}
diff --git a/header/src/main/java/org/zstack/header/vm/VmNicLifecycleExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/VmNicLifecycleExtensionPoint.java
new file mode 100644
index 00000000000..a90b790366d
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/vm/VmNicLifecycleExtensionPoint.java
@@ -0,0 +1,121 @@
+package org.zstack.header.vm;
+
+import org.zstack.header.core.Completion;
+import org.zstack.header.core.NoErrorCompletion;
+
+import java.util.List;
+
+/**
+ * Extension point for managing the lifecycle of VM NICs on hosts.
+ * Implementations are invoked during VM start, stop, migration, NIC attach/detach,
+ * and periodic reconciliation driven by KVM heartbeat.
+ *
+ * All async methods must invoke their completion callback exactly once,
+ * even on error paths. {@link Completion}-based methods may fail the operation;
+ * {@link NoErrorCompletion}-based methods must always succeed (log and absorb errors).
+ */
+public interface VmNicLifecycleExtensionPoint {
+
+ /**
+ * Returns true if this extension should manage the given NIC.
+ * Called synchronously; must not block or throw checked exceptions.
+ *
+ * @param nic the NIC to evaluate
+ * @return true if this extension handles the NIC
+ */
+ boolean isApplicable(VmNicInventory nic);
+
+ /**
+ * Called when a VM starts or a NIC is attached to a running VM.
+ * Failure aborts the VM start / NIC attach operation.
+ *
+ * @param hostUuid UUID of the host where the VM is starting
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code success()} or {@code fail()} exactly once
+ */
+ void setupOnHost(String hostUuid, List nics, Completion completion);
+
+ default void setupOnHost(VmNicLifecycleContext context, String hostUuid,
+ List nics, Completion completion) {
+ setupOnHost(hostUuid, nics, completion);
+ }
+
+ /**
+ * Called when a VM stops or a NIC is detached. Errors are logged but do not
+ * block the operation.
+ *
+ * @param hostUuid UUID of the host the VM is leaving
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ void cleanupFromHost(String hostUuid, List nics, NoErrorCompletion completion);
+
+ /**
+ * Called before live migration starts. Default: setup on destination host.
+ * Failure aborts the migration.
+ * Execution order: preMigrate → (live migration) → postMigrate or failedMigrate.
+ *
+ * @param srcHostUuid UUID of the source host
+ * @param destHostUuid UUID of the destination host
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code success()} or {@code fail()} exactly once
+ */
+ default void preMigrate(String srcHostUuid, String destHostUuid,
+ List nics, Completion completion) {
+ setupOnHost(destHostUuid, nics, completion);
+ }
+
+ /**
+ * Called after live migration succeeds. Errors are logged but do not block.
+ *
+ * @param srcHostUuid UUID of the source host
+ * @param destHostUuid UUID of the destination host
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ default void postMigrate(String srcHostUuid, String destHostUuid,
+ List nics, NoErrorCompletion completion) {
+ cleanupFromHost(srcHostUuid, nics, completion);
+ }
+
+ /**
+ * Called when live migration fails. Errors are logged but do not block.
+ *
+ * @param srcHostUuid UUID of the source host
+ * @param destHostUuid UUID of the destination host (where partial setup may exist)
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ default void failedMigrate(String srcHostUuid, String destHostUuid,
+ List nics, NoErrorCompletion completion) {
+ cleanupFromHost(destHostUuid, nics, completion);
+ }
+
+ /**
+ * Called on VM start when the VM's last known host differs from the destination host.
+ * Used to clean up state left on the previous host after an ungraceful shutdown.
+ * Errors are logged but do not block.
+ *
+ * @param lastHostUuid UUID of the host where the VM last ran
+ * @param nics NICs filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ default void cleanupStaleResource(String lastHostUuid, List nics,
+ NoErrorCompletion completion) {
+ cleanupFromHost(lastHostUuid, nics, completion);
+ }
+
+ /**
+ * Called periodically on each successful KVM heartbeat to reconcile NIC state.
+ * Implementations should ensure remote systems match the expected state in {@code expectedNics}.
+ * Errors are logged but do not block the heartbeat.
+ *
+ * @param hostUuid UUID of the host being reconciled
+ * @param expectedNics all Running-VM NICs on this host, filtered by {@link #isApplicable}
+ * @param completion call {@code done()} exactly once
+ */
+ default void reconcileOnHost(String hostUuid, List expectedNics,
+ NoErrorCompletion completion) {
+ completion.done();
+ }
+}
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/identity/src/main/java/org/zstack/identity/login/LoginManagerImpl.java b/identity/src/main/java/org/zstack/identity/login/LoginManagerImpl.java
index 45086320dba..b7cf5370ba6 100644
--- a/identity/src/main/java/org/zstack/identity/login/LoginManagerImpl.java
+++ b/identity/src/main/java/org/zstack/identity/login/LoginManagerImpl.java
@@ -267,6 +267,7 @@ private SessionInventory processSession(LoginSessionInfo info) {
session.setUserType(info.getUserType());
} else {
session = Session.login(info.getAccountUuid(), info.getUserUuid());
+ session.setUserType(info.getUserType());
}
return session;
diff --git a/longjob/src/main/java/org/zstack/longjob/LongJobManagerImpl.java b/longjob/src/main/java/org/zstack/longjob/LongJobManagerImpl.java
index 975b0e711db..79a01a76efa 100755
--- a/longjob/src/main/java/org/zstack/longjob/LongJobManagerImpl.java
+++ b/longjob/src/main/java/org/zstack/longjob/LongJobManagerImpl.java
@@ -613,6 +613,9 @@ private void handle(SubmitLongJobMsg msg) {
vo.setTargetResourceUuid(msg.getTargetResourceUuid());
vo.setManagementNodeUuid(Platform.getManagementServerId());
vo.setAccountUuid(msg.getAccountUuid());
+ Timestamp now = Timestamp.valueOf(LocalDateTime.now());
+ vo.setCreateDate(now);
+ vo.setLastOpDate(now);
vo = dbf.persistAndRefresh(vo);
msg.setJobUuid(vo.getUuid());
tagMgr.createTags(msg.getSystemTags(), msg.getUserTags(), vo.getUuid(), LongJobVO.class.getSimpleName());
@@ -831,9 +834,7 @@ public void validateGlobalConfig(String category, String name, String oldValue,
dbf.installEntityLifeCycleCallback(LongJobVO.class, EntityEvent.PRE_UPDATE, (evt, o) -> {
LongJobVO job = (LongJobVO) o;
if (job.getExecuteTime() == null && jobCompleted(job)) {
- long time = (System.currentTimeMillis() - job.getCreateDate().getTime()) / 1000;
- job.setExecuteTime(Long.max(time, 1));
- logger.info(String.format("longjob [uuid:%s] set execute time:%d", job.getUuid(), time));
+ setExecuteTimeIfNeed(job);
}
});
diff --git a/longjob/src/main/java/org/zstack/longjob/LongJobUtils.java b/longjob/src/main/java/org/zstack/longjob/LongJobUtils.java
index 2d6d2fabc8e..c4d37f98ebe 100644
--- a/longjob/src/main/java/org/zstack/longjob/LongJobUtils.java
+++ b/longjob/src/main/java/org/zstack/longjob/LongJobUtils.java
@@ -202,9 +202,10 @@ private static boolean isRecoverableError(ErrorCode errorCode) {
return recoverable instanceof Boolean && (Boolean) recoverable;
}
- private static void setExecuteTimeIfNeed(LongJobVO job) {
+ static void setExecuteTimeIfNeed(LongJobVO job) {
if (job.getExecuteTime() == null) {
- long time = (System.currentTimeMillis() - job.getCreateDate().getTime()) / 1000;
+ long startTime = job.getCreateDate() == null ? System.currentTimeMillis() : job.getCreateDate().getTime();
+ long time = (System.currentTimeMillis() - startTime) / 1000;
job.setExecuteTime(Long.max(time, 1));
logger.info(String.format("longjob [uuid:%s] set execute time:%d.", job.getUuid(), time));
}
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 40786eb7b2e..9b4ab106303 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())
@@ -113,6 +115,13 @@ private void validate(APIDeleteL2NetworkMsg msg) {
}
}
+ private boolean hasSdnControllerTag(List systemTags) {
+ if (systemTags == null || systemTags.isEmpty()) {
+ return false;
+ }
+ return systemTags.stream().anyMatch(tag -> tag.startsWith(L2NetworkSystemTags.L2_NETWORK_SDN_CONTROLLER_UUID_TOKEN + "::"));
+ }
+
private void validate(APICreateL2NetworkMsg msg) {
if (!L2NetworkType.hasType(msg.getType())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10011, "unsupported l2Network type[%s]", msg.getType()));
@@ -124,8 +133,11 @@ private void validate(APICreateL2NetworkMsg msg) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10012, "unsupported vSwitch type[%s]", msg.getvSwitchType()));
}
+ msg.setPhysicalInterface(StringUtils.trimToNull(msg.getPhysicalInterface()));
+
if (L2NetworkConstant.VSWITCH_TYPE_LINUX_BRIDGE.equals(msg.getvSwitchType())
- && (msg.getPhysicalInterface() == null || msg.getPhysicalInterface().trim().isEmpty())) {
+ && msg.getPhysicalInterface() == null
+ && !hasSdnControllerTag(msg.getSystemTags())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10021,
"physicalInterface is required when vSwitchType is [%s]", msg.getvSwitchType()));
}
@@ -133,26 +145,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()));
@@ -163,7 +194,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" +
@@ -196,7 +229,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()));
@@ -204,7 +237,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" +
@@ -233,6 +268,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 || targetIsVxlan) {
+ if (msg.getVlan() == null) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10016, "vni is required for " +
+ "ChangeL2NetworkVlanId with type[%s]", msg.getType()));
+ }
+ if (!NetworkUtils.isValidVni(msg.getVlan())) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10017, "invalid vni[%d] for " +
+ "ChangeL2NetworkVlanId, must be between 1 and %d", msg.getVlan(), L2NetworkConstant.VXLAN_ID_MAX));
+ }
}
}
}
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/L3BasicNetwork.java b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java
index d7c6c6798d9..1e4f046423d 100755
--- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java
+++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java
@@ -1047,6 +1047,7 @@ public void fail(ErrorCode errorCode) {
private void handle(APIUpdateIpRangeMsg msg) {
IpRangeVO vo = dbf.findByUuid(msg.getUuid(), IpRangeVO.class);
+ IpRangeInventory oldIpr = IpRangeInventory.valueOf(vo);
boolean update = false;
if (msg.getName() != null) {
vo.setName(msg.getName());
@@ -1059,8 +1060,14 @@ private void handle(APIUpdateIpRangeMsg msg) {
if (update) {
vo = dbf.updateAndRefresh(vo);
}
+
+ IpRangeInventory newIpr = IpRangeInventory.valueOf(vo);
+ for (AfterUpdateIpRangeExtensionPoint ext : pluginRgty.getExtensionList(AfterUpdateIpRangeExtensionPoint.class)) {
+ ext.afterUpdateIpRange(oldIpr, newIpr);
+ }
+
APIUpdateIpRangeEvent evt = new APIUpdateIpRangeEvent(msg.getId());
- evt.setInventory(IpRangeInventory.valueOf(vo));
+ evt.setInventory(newIpr);
bus.publish(evt);
}
@@ -1534,12 +1541,14 @@ public void done(ErrorCodeList errorCodeList) {
detachNetworkServiceFromL3NetworkMsg(l3VO, refVOS, new Completion(msg) {
@Override
public void success() {
+ dbf.removeCollection(refVOS, NetworkServiceL3NetworkRefVO.class);
reply.setError(errorCodeList.getCauses().get(0));
bus.reply(msg, reply);
}
@Override
public void fail(ErrorCode errorCode) {
+ dbf.removeCollection(refVOS, NetworkServiceL3NetworkRefVO.class);
reply.setError(errorCodeList.getCauses().get(0));
bus.reply(msg, reply);
}
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 81fb7e94b3f..45fecdc0aa1 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) {
@@ -192,8 +227,8 @@ public void handle(Map data) {
}).error(new FlowErrorHandler(msg) {
@Override
public void handle(ErrorCode errCode, Map data) {
+ NetworkServiceSystemTag.L3_MTU.delete(msg.getL3NetworkUuid());
if (oldmtu != null) {
- NetworkServiceSystemTag.L3_MTU.delete(msg.getL3NetworkUuid());
SystemTagCreator creator = NetworkServiceSystemTag.L3_MTU.newSystemTagCreator(msg.getL3NetworkUuid());
creator.recreate = true;
creator.inherent = false;
@@ -518,6 +553,7 @@ private void handle(APICreateL3NetworkMsg msg) {
vo.setIpVersion(IPv6Constants.IPv4);
}
vo.setInternalId((int)dbf.generateSequenceNumber(L3NetworkSequenceNumberVO.class));
+ vo.setType(msg.getType() != null ? msg.getType() : L3NetworkConstant.L3_BASIC_NETWORK_TYPE);
FlowChain fchain = new SimpleFlowChain();
fchain.setName(String.format("create-l3-network-%s", vo.getUuid()));
diff --git a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
index dcada8edeee..094771f24bb 100755
--- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
+++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
@@ -47,6 +47,7 @@ public static enum Params {
timeout,
rebuildSnat,
fromApi,
+ skipGrayscaleUpgradeCheck,
}
public static final String REFRESH_FIREWALL_PATH = "/appliancevm/refreshfirewall";
diff --git a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
index 6d63ce3522f..76d98729668 100755
--- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
+++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
@@ -296,7 +296,7 @@ class VmDestroyHook implements VmInstanceBeforeDestroyHook, VmInstanceAfterDestr
@Override
public void afterDestroy(VmInstanceInventory vm) {
if (applianceVm != null) {
- ApplianceVmCanonicalEvents.NewVmCreatedData data = new ApplianceVmCanonicalEvents.NewVmCreatedData();
+ ApplianceVmCanonicalEvents.VmDestroyedData data = new ApplianceVmCanonicalEvents.VmDestroyedData();
data.vm = applianceVm;
data.fire();
}
diff --git a/plugin/externalStorage/src/main/java/org/zstack/externalStorage/primary/kvm/ExternalPrimaryStorageKvmFactory.java b/plugin/externalStorage/src/main/java/org/zstack/externalStorage/primary/kvm/ExternalPrimaryStorageKvmFactory.java
index 6e5c0c591b8..4724d20aed7 100644
--- a/plugin/externalStorage/src/main/java/org/zstack/externalStorage/primary/kvm/ExternalPrimaryStorageKvmFactory.java
+++ b/plugin/externalStorage/src/main/java/org/zstack/externalStorage/primary/kvm/ExternalPrimaryStorageKvmFactory.java
@@ -141,6 +141,27 @@ public void done(ErrorCodeList errorCodeList) {
}
});
}
+ }).then(new NoRollbackFlow() {
+ String __name__ = "ensure-heartbeat-volume";
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ ensureHeartbeatVolume(context.getInventory(), extPss, new WhileDoneCompletion(trigger) {
+ @Override
+ public void done(ErrorCodeList errorCodeList) {
+ if (errorCodeList.getCauses().isEmpty()) {
+ trigger.next();
+ } else {
+ logger.warn(String.format("failed to ensure heartbeat volumes before checking KVM host[uuid:%s, name:%s] storage connection, %s",
+ context.getInventory().getUuid(), context.getInventory().getName(), errorCodeList.getReadableDetails()));
+ trigger.fail(operr(ORG_ZSTACK_EXTERNALSTORAGE_PRIMARY_KVM_10000,
+ new ErrorCodeList().causedBy(errorCodeList.getCauses()),
+ "failed to ensure heartbeat volumes before checking KVM host[uuid:%s, name:%s] storage connection",
+ context.getInventory().getUuid(), context.getInventory().getName()));
+ }
+ }
+ });
+ }
}).then(new NoRollbackFlow() {
String __name__ = "check-host-status";
@@ -167,6 +188,25 @@ public void handle(Map data) {
}).start();
}
+ private void ensureHeartbeatVolume(HostInventory host, List extPss, WhileDoneCompletion completion) {
+ new While<>(extPss).each((extPs, compl) -> {
+ logger.debug(String.format("ensuring heartbeat volume for external primary storage[uuid:%s, name:%s] before checking KVM host[uuid:%s, name:%s] storage connection",
+ extPs.getUuid(), extPs.getName(), host.getUuid(), host.getName()));
+ extPsFactory.getNodeSvc(extPs.getUuid()).activateHeartbeatVolume(host, new ReturnValueCompletion(compl) {
+ @Override
+ public void success(HeartbeatVolumeTopology returnValue) {
+ compl.done();
+ }
+
+ @Override
+ public void fail(ErrorCode errorCode) {
+ compl.addError(errorCode);
+ compl.done();
+ }
+ });
+ }).run(completion);
+ }
+
private void deployClient(final KVMHostConnectedContext context, List extPss, WhileDoneCompletion completion) {
new While<>(extPss).each((extPs, compl) -> {
logger.debug(String.format("deploying client for external primary storage[uuid:%s, name:%s] on KVM host[uuid:%s, name:%s]",
diff --git a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatUserdataBackend.java b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatUserdataBackend.java
index 15ef662ad80..f6d15a229aa 100755
--- a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatUserdataBackend.java
+++ b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatUserdataBackend.java
@@ -310,10 +310,6 @@ private List getUserData() {
continue;
}
- if (mto.vmHostname == null) {
- mto.vmHostname = l.vmIp.replaceAll("\\.", "-");
- }
-
if (bridgeNames.get(l.l3Uuid) == null) {
continue;
}
@@ -784,9 +780,6 @@ public void run(final FlowTrigger trigger, Map data) {
MetadataTO to = new MetadataTO();
to.vmUuid = struct.getVmUuid();
to.vmHostname = VmSystemTags.HOSTNAME.getTokenByResourceUuid(struct.getVmUuid(), VmSystemTags.HOSTNAME_TOKEN);
- if (to.vmHostname == null) {
- to.vmHostname = ipv4.getIp().replaceAll("\\.", "-");
- }
to.regionName = getZoneNameByVmInstanceUuid(struct.getVmUuid());
to.mac = destNic.getMac();
to.dnsServersIp = getDnsServersIpFromVm(struct.getVmUuid());
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 90e576cad7f..6a47215bd23 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
@@ -1014,6 +1014,42 @@ public static class SetVmConsolePasswordLiveCmd extends AgentCommand implements
public void setPassword(String password) { this.password = password; }
}
+ public static class SetupVmHaEnabledMetadataLiveCmd extends AgentCommand implements Serializable {
+ @GrayVersion(value = "5.5.22")
+ private String vmUuid;
+ @GrayVersion(value = "5.5.22")
+ private Boolean enableHa;
+
+ public String getVmUuid() {
+ return vmUuid;
+ }
+
+ public void setVmUuid(String vmUuid) {
+ this.vmUuid = vmUuid;
+ }
+
+ public Boolean getEnableHa() {
+ return enableHa;
+ }
+
+ public void setEnableHa(Boolean enableHa) {
+ this.enableHa = enableHa;
+ }
+ }
+
+ public static class ReconcileVmHaEnabledMetadataLiveCmd extends AgentCommand implements Serializable {
+ @GrayVersion(value = "5.5.22")
+ private List neverStopVmUuids;
+
+ public List getNeverStopVmUuids() {
+ return neverStopVmUuids;
+ }
+
+ public void setNeverStopVmUuids(List neverStopVmUuids) {
+ this.neverStopVmUuids = neverStopVmUuids;
+ }
+ }
+
public static class UpdateL2NetworkCmd extends AgentCommand {
private String physicalInterfaceName;
private String bridgeName;
@@ -1242,6 +1278,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 +1478,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());
@@ -2236,6 +2296,8 @@ public static class StartVmCmd extends vdiCmd implements VmAddOnsCmd {
private String nestedVirtualization;
@GrayVersion(value = "5.0.0")
private String hostManagementIp;
+ @GrayVersion(value = "5.5.22")
+ private Boolean enableHa;
@GrayVersion(value = "5.0.0")
private String clock;
@GrayVersion(value = "5.0.0")
@@ -2726,6 +2788,14 @@ public void setHostManagementIp(String hostManagementIp) {
this.hostManagementIp = hostManagementIp;
}
+ public Boolean getEnableHa() {
+ return enableHa;
+ }
+
+ public void setEnableHa(Boolean enableHa) {
+ this.enableHa = enableHa;
+ }
+
public VolumeTO getRootVolume() {
return rootVolume;
}
@@ -4972,6 +5042,8 @@ public static class OvsAddPortCmd extends AgentCommand {
public Map nicMap = new HashMap<>();
@GrayVersion(value = "5.4.0")
public Map nicVmInstanceUuidMap = new HashMap<>();
+ @GrayVersion(value = "5.5.22")
+ public Map ifaceIdMap = new HashMap<>();
}
public static class OvsAddPortRsp extends AgentResponse {
@@ -4984,6 +5056,8 @@ public static class OvsSyncPortCmd extends AgentCommand {
public Map nicMap = new HashMap<>();
@GrayVersion(value = "5.4.0")
public String vmUuid;
+ @GrayVersion(value = "5.5.22")
+ public Map ifaceIdMap = new HashMap<>();
}
public static class OvsSyncPortRsp extends AgentResponse {
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/KVMConstant.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
index 303a12bc6fc..5f08e6fdb98 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
@@ -97,6 +97,11 @@ public interface KVMConstant {
String CLEAN_FIRMWARE_FLASH = "/clean/firmware/flash";
String FSTRIM_VM_PATH = "/vm/fstrim";
+ // ZSTAC-83157: virtiofs model mount paths
+ String KVM_VIRTIOFS_ATTACH_PATH = "/virtiofs/attach";
+ String KVM_VIRTIOFS_DETACH_PATH = "/virtiofs/detach";
+ String KVM_MODEL_CENTER_MOUNT_PATH = "/modelcenter/mount";
+
String ISO_TO = "kvm.isoto";
String ANSIBLE_PLAYBOOK_NAME = "kvm.py";
String ANSIBLE_MODULE_PATH = "ansible/kvm";
@@ -128,6 +133,9 @@ public interface KVMConstant {
String KVM_BLOCK_PULL_VOLUME_PATH = "/vm/volume/blockpull";
String TAKE_VM_CONSOLE_SCREENSHOT_PATH = "/vm/console/screenshot";
String UPDATE_VM_CONSOLE_PASSWORD_PATH = "/host/vm/updateConsolePassword/live";
+ String SETUP_VM_HA_ENABLED_METADATA_LIVE_PATH = "/host/vm/setupHaEnabledMetadata/live";
+ String RECONCILE_VM_HA_ENABLED_METADATA_LIVE_PATH = "/host/vm/reconcileHaEnabledMetadata/live";
+ String HA_NETWORK_GROUP_SYNC_PATH = "/ha/networkgroup/sync";
String KVM_HOST_IPSET_ATTACH_NIC_PATH = "/network/ipset/attach";
String KVM_HOST_IPSET_DETACH_NIC_PATH = "/network/ipset/detach";
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 8696757bc92..34f02f6c274 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
@@ -73,6 +73,7 @@
import org.zstack.header.rest.JsonAsyncRESTCallback;
import org.zstack.header.rest.RESTFacade;
import org.zstack.header.storage.primary.*;
+import org.zstack.header.storage.snapshot.*;
import org.zstack.header.tag.SystemTagInventory;
import org.zstack.header.vm.*;
import org.zstack.header.vm.devices.DeviceAddress;
@@ -3058,6 +3059,7 @@ public void success(TakeSnapshotResponse ret) {
" data corruption may happen", ret.getNewVolumeInstallPath()));
}
+ syncSnapshotMetadataAfterHypervisorSuccess(msg, ret);
extEmitter.afterTakeSnapshot((KVMHostInventory) getSelfInventory(), msg, cmd, ret);
reply.setNewVolumeInstallPath(ret.getNewVolumeInstallPath());
reply.setSnapshotInstallPath(ret.getSnapshotInstallPath());
@@ -3094,6 +3096,27 @@ public void handle(ErrorCode errCode, Map data) {
}).start();
}
+ private void syncSnapshotMetadataAfterHypervisorSuccess(TakeSnapshotOnHypervisorMsg msg, TakeSnapshotResponse ret) {
+ VolumeInventory volume = msg.getVolume();
+ if (volume == null || msg.getSnapshotName() == null) {
+ return;
+ }
+
+ VolumeSnapshotInventory snapshot = new VolumeSnapshotInventory();
+ snapshot.setUuid(msg.getSnapshotName());
+ snapshot.setVolumeUuid(volume.getUuid());
+ snapshot.setPrimaryStorageUuid(volume.getPrimaryStorageUuid());
+ snapshot.setPrimaryStorageInstallPath(ret.getSnapshotInstallPath());
+ snapshot.setType(VolumeSnapshotConstant.HYPERVISOR_SNAPSHOT_TYPE.toString());
+ snapshot.setStatus(VolumeSnapshotStatus.Ready.toString());
+ snapshot.setSize(ret.getSize());
+ snapshot.setFormat(volume.getFormat());
+
+ for (VolumeSnapshotDBSyncExtensionPoint ext : pluginRgty.getExtensionList(VolumeSnapshotDBSyncExtensionPoint.class)) {
+ ext.syncVolumeSnapshotDBAfterTakeSnapshot(volume, snapshot, ret.getNewVolumeInstallPath());
+ }
+ }
+
private void migrateVm(final MigrateStruct s, final Completion completion) {
final TaskProgressRange parentStage = getTaskStage();
final TaskProgressRange MIGRATE_VM_STAGE = new TaskProgressRange(0, 90);
@@ -4198,6 +4221,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));
@@ -5716,74 +5749,62 @@ public void run(FlowTrigger trigger, Map data) {
@Override
public boolean skip(Map data) {
+ // ZSTAC-84446: run detection whenever TLS is enabled so check
+ // and first-deploy share the same IP source.
return CoreGlobalProperty.UNIT_TEST_ON
- || !KVMGlobalConfig.LIBVIRT_TLS_ENABLED.value(Boolean.class)
- || !rcf.getResourceConfigValue(
- KVMGlobalConfig.RECONNECT_HOST_RESTART_LIBVIRTD_SERVICE,
- self.getUuid(), Boolean.class);
+ || !KVMGlobalConfig.LIBVIRT_TLS_ENABLED.value(Boolean.class);
}
@Override
public void run(FlowTrigger trigger, Map data) {
- String managementIp = getSelf().getManagementIp();
-
- // 1. Get all IPs on the host via SSH
- SshShell sshShell = new SshShell();
- sshShell.setHostname(managementIp);
- sshShell.setUsername(getSelf().getUsername());
- sshShell.setPassword(getSelf().getPassword());
- sshShell.setPort(getSelf().getPort());
-
- SshResult ipResult = sshShell.runCommand(
- "ip -4 -o addr show scope global | sed -n \"s/.* inet \\([0-9.]\\+\\).*/\\1/p\"");
- if (ipResult.isSshFailure() || ipResult.getReturnCode() != 0) {
- logger.warn(String.format("Failed to get host IPs via SSH for TLS cert check on host[uuid:%s]: %s",
- self.getUuid(), ipResult.getExitErrorMessage()));
- trigger.next();
- return;
- }
-
- // 2. Build IP list: managementIp + extra IPs (exclude managementIp and MN VIP)
- List allIps = new ArrayList<>();
- allIps.add(managementIp);
- String[] hostIps = ipResult.getStdout().trim().split("\n");
- for (String ip : hostIps) {
- String trimmed = ip.trim();
- if (!trimmed.isEmpty() && !trimmed.equals(managementIp)
- && !trimmed.equals(CoreGlobalProperty.MN_VIP)
- && !trimmed.equals("127.0.0.1")
- && !allIps.contains(trimmed)) {
- allIps.add(trimmed);
- }
- }
-
- String certIpList = String.join(",", allIps);
-
- // 3. Check existing cert SAN via SSH
- SshResult sanResult = sshShell.runCommand(
- "openssl x509 -in /etc/pki/libvirt/servercert.pem -noout -ext subjectAltName 2>/dev/null");
+ // ZSTAC-84446: detection is best-effort. SSH failures must NOT
+ // break reconnect; on error we skip and let the deploy step
+ // fall back to mgmtIp + EXTRA_IPS.
+ try {
+ String managementIp = getSelf().getManagementIp();
- boolean needDeploy = false;
- if (sanResult.isSshFailure() || sanResult.getReturnCode() != 0
- || sanResult.getStdout() == null || sanResult.getStdout().trim().isEmpty()) {
- // cert doesn't exist or can't be read
- logger.info(String.format("TLS cert not found or unreadable on host[uuid:%s], need deploy", self.getUuid()));
- needDeploy = true;
- } else {
- Set sanIps = parseSanIps(sanResult.getStdout());
- for (String ip : allIps) {
- if (!sanIps.contains(ip)) {
- logger.info(String.format("TLS cert SAN missing IP %s on host[uuid:%s], need deploy", ip, self.getUuid()));
- needDeploy = true;
- break;
+ SshShell sshShell = new SshShell();
+ sshShell.setHostname(managementIp);
+ sshShell.setUsername(getSelf().getUsername());
+ sshShell.setPassword(getSelf().getPassword());
+ sshShell.setPort(getSelf().getPort());
+
+ // Same logic as zstack-utility host_plugin.fact() so MN's
+ // expectation matches what the host itself reports.
+ String certIpList = KVMHostUtils.collectHostIps(
+ sshShell, self.getUuid(), managementIp);
+ List allIps = new ArrayList<>(Arrays.asList(certIpList.split(",")));
+ // Save detected IPs so apply-ansible-playbook can union with
+ // EXTRA_IPS without running a second SSH.
+ data.put("TLS_DETECTED_IPS", certIpList);
+
+ SshResult sanResult = sshShell.runCommand(
+ "openssl x509 -in /etc/pki/libvirt/servercert.pem -noout -ext subjectAltName 2>/dev/null");
+
+ boolean needDeploy = false;
+ if (sanResult.isSshFailure() || sanResult.getReturnCode() != 0
+ || sanResult.getStdout() == null || sanResult.getStdout().trim().isEmpty()) {
+ logger.info(String.format("TLS cert not found or unreadable on host[uuid:%s], need deploy", self.getUuid()));
+ needDeploy = true;
+ } else {
+ Set sanIps = parseSanIps(sanResult.getStdout());
+ for (String ip : allIps) {
+ if (!sanIps.contains(ip)) {
+ logger.info(String.format("TLS cert SAN missing IP %s on host[uuid:%s], need deploy", ip, self.getUuid()));
+ needDeploy = true;
+ break;
+ }
}
}
- }
- if (needDeploy) {
- data.put("NEED_DEPLOY_TLS_CERT", true);
+ if (needDeploy) {
+ data.put("NEED_DEPLOY_TLS_CERT", true);
+ }
+ } catch (Exception e) {
+ logger.warn(String.format(
+ "TLS cert detection failed on host[uuid:%s], continue connect flow: %s",
+ self.getUuid(), e.getMessage()), e);
}
- data.put("TLS_CERT_IPS", certIpList);
trigger.next();
}
@@ -5923,27 +5944,25 @@ public void run(final FlowTrigger trigger, Map data) {
deployArguments.setSkipPackages(info.getSkipPackages());
deployArguments.setUpdatePackages(String.valueOf(CoreGlobalProperty.UPDATE_PKG_WHEN_CONNECT));
- // Build TLS cert IP list: prefer SSH-detected IPs from check-tls-certs flow
- String tlsCertIpsFromData = (String) data.get("TLS_CERT_IPS");
- if (tlsCertIpsFromData != null) {
- deployArguments.setTlsCertIps(tlsCertIpsFromData);
- } else {
- // Fallback: management IP + extra IPs from system tag
- String managementIp = getSelf().getManagementIp();
- String extraIps = HostSystemTags.EXTRA_IPS.getTokenByResourceUuid(
- self.getUuid(), HostSystemTags.EXTRA_IPS_TOKEN);
- if (extraIps != null && !extraIps.isEmpty()) {
- deployArguments.setTlsCertIps(managementIp + "," + extraIps);
- } else {
- deployArguments.setTlsCertIps(managementIp);
- }
- }
+ String managementIp = getSelf().getManagementIp();
+ String detectedIps = (String) data.get("TLS_DETECTED_IPS");
+ String tlsCertIps = KVMHostUtils.unionTlsCertIps(
+ self.getUuid(), managementIp, detectedIps);
+ deployArguments.setTlsCertIps(tlsCertIps);
- // Force ansible deploy when TLS cert needs update (detected by check-tls-certs flow)
+ // ZSTAC-84446: force ansible re-run only when policy allows;
+ // see KVMHostUtils#shouldForceTlsRedeploy.
Boolean needDeployTlsCert = (Boolean) data.get("NEED_DEPLOY_TLS_CERT");
- if (Boolean.TRUE.equals(needDeployTlsCert)) {
+ boolean allowRestart = rcf.getResourceConfigValue(
+ KVMGlobalConfig.RECONNECT_HOST_RESTART_LIBVIRTD_SERVICE,
+ self.getUuid(), Boolean.class);
+ if (KVMHostUtils.shouldForceTlsRedeploy(
+ Boolean.TRUE.equals(needDeployTlsCert), allowRestart, info.isNewAdded())) {
runner.setForceRun(true);
deployArguments.setRestartLibvirtd("true");
+ } else if (Boolean.TRUE.equals(needDeployTlsCert)) {
+ logger.info(String.format("TLS cert needs deploy on host[uuid:%s], skip " +
+ "force-run to keep kvmagent PID stable", self.getUuid()));
}
if (deployArguments.isForceRun()) {
@@ -5971,6 +5990,13 @@ public void success(Boolean run) {
@Override
public void fail(ErrorCode errorCode) {
+ if (KVMHostUtils.shouldContinueReconnectOnAnsibleFailure(info.isNewAdded(), errorCode)) {
+ logger.warn(String.format(
+ "kvm ansible failed to mask libvirt sockets because systemd dbus timed out on existing host[uuid:%s, ip:%s], continue reconnect and verify kvmagent, error: %s",
+ self.getUuid(), self.getManagementIp(), errorCode));
+ trigger.next();
+ return;
+ }
trigger.fail(errorCode);
}
});
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 0188c920a83..513add98a83 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java
@@ -50,6 +50,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;
@@ -380,7 +381,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/KVMHostUtils.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java
index cf6d16e7560..53db539044b 100644
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java
@@ -1,18 +1,26 @@
package org.zstack.kvm;
import org.apache.commons.codec.digest.DigestUtils;
+import org.zstack.compute.host.HostSystemTags;
+import org.zstack.core.CoreGlobalProperty;
import org.zstack.core.db.Q;
import org.zstack.header.network.l2.*;
+import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.tag.SystemTagVO;
import org.zstack.header.tag.SystemTagVO_;
import org.zstack.header.tag.TagType;
+import org.zstack.utils.CollectionUtils;
import org.zstack.utils.TagUtils;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.logging.CLoggerImpl;
+import org.zstack.utils.ssh.SshResult;
+import org.zstack.utils.ssh.SshShell;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
/**
@@ -21,6 +29,165 @@
public class KVMHostUtils {
private static final CLogger logger = CLoggerImpl.getLogger(KVMHostUtils.class);
+ // ZSTAC-84446: br_conn_all_ns is host-internal; exclude from TLS cert SAN
+ // to keep check-flow and deploy-flow IP lists identical.
+ public static final Set EXCLUDED_INTERNAL_IPS = Collections.unmodifiableSet(
+ new LinkedHashSet<>(Collections.singletonList("169.254.64.1")));
+
+ // Collect host IPv4 addresses; mirrors host_plugin.fact() filters and
+ // applies EXCLUDED_INTERNAL_IPS so check and deploy share one source.
+ public static String collectHostIps(SshShell sshShell, String hostUuid, String managementIp) {
+ if (sshShell == null) {
+ return managementIp;
+ }
+ // Quote-free command; parsed on MN side to avoid SshShell quote mangling.
+ SshResult r = sshShell.runCommand("ip -4 -o addr show");
+ if (r.isSshFailure() || r.getReturnCode() != 0
+ || r.getStdout() == null || r.getStdout().trim().isEmpty()) {
+ logger.warn(String.format(
+ "ssh-collect host IPs failed on host[uuid:%s], fallback to mgmtIp: %s",
+ hostUuid, r.getExitErrorMessage()));
+ return managementIp;
+ }
+ return buildIpList(managementIp, r.getStdout(), CoreGlobalProperty.MN_VIP);
+ }
+
+ // TLS cert IPs for ansible deploy: detectedIps ∪ EXTRA_IPS − EXCLUDED_INTERNAL_IPS,
+ // falls back to managementIp when detectedIps is empty. Shares filter with collectHostIps.
+ public static String unionTlsCertIps(String hostUuid, String managementIp, String detectedIpsCsv) {
+ String extraIps = HostSystemTags.EXTRA_IPS.getTokenByResourceUuid(
+ hostUuid, HostSystemTags.EXTRA_IPS_TOKEN);
+ return unionIps(detectedIpsCsv, managementIp, extraIps,
+ CoreGlobalProperty.MN_VIP, EXCLUDED_INTERNAL_IPS);
+ }
+
+ public static String unionIps(String detectedIpsCsv, String managementIp,
+ String extraIpsCsv, String mnVip,
+ Set excludedInternalIps) {
+ Set ips = new LinkedHashSet<>();
+ if (detectedIpsCsv != null && !detectedIpsCsv.trim().isEmpty()) {
+ for (String ip : detectedIpsCsv.split(",")) {
+ String t = ip.trim();
+ if (!t.isEmpty()) {
+ ips.add(t);
+ }
+ }
+ } else {
+ ips.add(managementIp);
+ }
+
+ if (extraIpsCsv != null && !extraIpsCsv.isEmpty()) {
+ for (String ip : extraIpsCsv.split(",")) {
+ String t = ip.trim();
+ if (!t.isEmpty()) {
+ ips.add(t);
+ }
+ }
+ }
+
+ return String.join(",", filterIps(ips, mnVip, excludedInternalIps));
+ }
+
+ // Single source of truth for TLS cert SAN IPs; shared by buildIpList (check)
+ // and unionIps (deploy) so the two flows can never diverge.
+ private static Set filterIps(Set ips, String mnVip,
+ Set excludedInternalIps) {
+ ips.remove("127.0.0.1");
+ if (mnVip != null) {
+ ips.remove(mnVip);
+ }
+ if (!CollectionUtils.isEmpty(excludedInternalIps)) {
+ ips.removeAll(excludedInternalIps);
+ }
+ return ips;
+ }
+
+ // Parse "ip -4 -o addr show" output and build the IP list, mirroring
+ // host_plugin.fact() (drop ifname *zs, 127.0.0.1, MN VIP, EXCLUDED_INTERNAL_IPS).
+ public static String buildIpList(String managementIp, String ipAddrOutput, String mnVip) {
+ Set ips = new LinkedHashSet<>();
+ ips.add(managementIp);
+
+ if (ipAddrOutput != null) {
+ for (String line : ipAddrOutput.trim().split("\n")) {
+ String[] parts = line.trim().split("\\s+");
+ // expect at least: ":" "" "inet" "/"
+ if (parts.length < 4 || !"inet".equals(parts[2])) {
+ continue;
+ }
+ String iface = parts[1];
+ if (iface.endsWith("zs")) {
+ continue;
+ }
+ String cidr = parts[3];
+ int slash = cidr.indexOf('/');
+ String ip = slash >= 0 ? cidr.substring(0, slash) : cidr;
+ if (!ip.isEmpty()) {
+ ips.add(ip);
+ }
+ }
+ }
+
+ return String.join(",", filterIps(ips, mnVip, EXCLUDED_INTERNAL_IPS));
+ }
+
+ public static String collectHostIps(String hostUuid, String managementIp,
+ String username, String password, int sshPort) {
+ return collectHostIps(newSsh(managementIp, username, password, sshPort), hostUuid, managementIp);
+ }
+
+ // ZSTAC-84446: force ansible re-run + libvirtd restart only when operator opted in
+ // or it's a fresh add; skip on plain reconnect to keep kvmagent PID stable.
+ public static boolean shouldForceTlsRedeploy(boolean needDeployTlsCert,
+ boolean allowRestartLibvirtd,
+ boolean isNewAdded) {
+ if (!needDeployTlsCert) {
+ return false;
+ }
+ return allowRestartLibvirtd || isNewAdded;
+ }
+
+ public static boolean shouldContinueReconnectOnAnsibleFailure(boolean isNewAdded, ErrorCode errorCode) {
+ return !isNewAdded && isLibvirtSocketMaskSystemdTimeout(errorCode);
+ }
+
+ public static boolean isLibvirtSocketMaskSystemdTimeout(ErrorCode errorCode) {
+ String errorText = collectErrorText(errorCode).toLowerCase(Locale.ROOT);
+ return errorText.contains("systemctl mask")
+ && errorText.contains("libvirtd.socket")
+ && errorText.contains("org.freedesktop.systemd1")
+ && errorText.contains("timed out")
+ && (errorText.contains("failed to get properties")
+ || errorText.contains("failed to activate service"));
+ }
+
+ private static String collectErrorText(ErrorCode errorCode) {
+ StringBuilder sb = new StringBuilder();
+ ErrorCode cursor = errorCode;
+ while (cursor != null) {
+ appendIfNotNull(sb, cursor.getDetails());
+ appendIfNotNull(sb, cursor.getDescription());
+ appendIfNotNull(sb, cursor.getMessage());
+ cursor = cursor.getCause();
+ }
+ return sb.toString();
+ }
+
+ private static void appendIfNotNull(StringBuilder sb, String text) {
+ if (text != null) {
+ sb.append(text).append('\n');
+ }
+ }
+
+ private static SshShell newSsh(String host, String user, String pwd, int port) {
+ SshShell s = new SshShell();
+ s.setHostname(host);
+ s.setUsername(user);
+ s.setPassword(pwd);
+ s.setPort(port);
+ return s;
+ }
+
/**
* Get normalized bridge name for l2 network, which at most has 15 chars.
* - if l2 network has L2_BRIDGE_NAME tag, then return it's value directly;
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/kvm/src/main/java/org/zstack/kvm/VmNicLifecycleKvmBridge.java b/plugin/kvm/src/main/java/org/zstack/kvm/VmNicLifecycleKvmBridge.java
new file mode 100644
index 00000000000..c1ecdd4c716
--- /dev/null
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/VmNicLifecycleKvmBridge.java
@@ -0,0 +1,114 @@
+package org.zstack.kvm;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.compute.vm.VmNicLifecycleGlobalConfig;
+import org.zstack.core.asyncbatch.While;
+import org.zstack.core.componentloader.PluginRegistry;
+import org.zstack.core.db.Q;
+import org.zstack.core.thread.ThreadFacade;
+import org.zstack.core.thread.ThreadFacadeImpl;
+import org.zstack.header.core.NoErrorCompletion;
+import org.zstack.header.core.WhileDoneCompletion;
+import org.zstack.header.errorcode.ErrorCodeList;
+import org.zstack.header.vm.*;
+import org.zstack.header.vm.VmNicLifecycleExtensionPoint;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+public class VmNicLifecycleKvmBridge implements KVMPingAgentNoFailureExtensionPoint {
+
+ private static final CLogger logger = Utils.getLogger(VmNicLifecycleKvmBridge.class);
+
+ @Autowired
+ private PluginRegistry pluginRgty;
+ @Autowired
+ private ThreadFacade thdf;
+
+ private List getExtensions() {
+ return pluginRgty.getExtensionList(VmNicLifecycleExtensionPoint.class);
+ }
+
+ @Override
+ public void kvmPingAgentNoFailure(KVMHostInventory host, NoErrorCompletion completion) {
+ List extensions = getExtensions();
+ if (extensions.isEmpty()) {
+ completion.done();
+ return;
+ }
+
+ String hostUuid = host.getUuid();
+
+ List allExpectedNics;
+ try {
+ List runningVms = Q.New(VmInstanceVO.class)
+ .eq(VmInstanceVO_.hostUuid, hostUuid)
+ .eq(VmInstanceVO_.state, VmInstanceState.Running)
+ .list();
+ allExpectedNics = runningVms.stream()
+ .flatMap(vm -> VmNicInventory.valueOf(vm.getVmNics()).stream())
+ .collect(Collectors.toList());
+ } catch (Exception e) {
+ logger.warn(String.format("[VmNicLifecycle] failed to query Running VMs " +
+ "on host[uuid:%s] for reconciliation, skip this round", hostUuid), e);
+ completion.done();
+ return;
+ }
+
+ long timeoutSeconds = VmNicLifecycleGlobalConfig.RECONCILE_TIMEOUT.value(Long.class);
+
+ new While<>(extensions).step((ext, whileCompletion) -> {
+ List matchedNics;
+ try {
+ matchedNics = allExpectedNics.stream()
+ .filter(ext::isApplicable)
+ .collect(Collectors.toList());
+ } catch (Exception e) {
+ logger.warn(String.format("[VmNicLifecycle] %s.isApplicable threw exception " +
+ "during reconciliation on host[uuid:%s]",
+ ext.getClass().getSimpleName(), hostUuid), e);
+ whileCompletion.done();
+ return;
+ }
+
+ AtomicBoolean completed = new AtomicBoolean(false);
+
+ ThreadFacadeImpl.TimeoutTaskReceipt receipt = thdf.submitTimeoutTask(() -> {
+ if (completed.compareAndSet(false, true)) {
+ logger.warn(String.format("[VmNicLifecycle] %s.reconcileOnHost timed out " +
+ "after %ds on host[uuid:%s]",
+ ext.getClass().getSimpleName(), timeoutSeconds, hostUuid));
+ whileCompletion.done();
+ }
+ }, TimeUnit.SECONDS, timeoutSeconds);
+
+ try {
+ ext.reconcileOnHost(hostUuid, matchedNics, new NoErrorCompletion() {
+ @Override
+ public void done() {
+ if (completed.compareAndSet(false, true)) {
+ receipt.cancel();
+ whileCompletion.done();
+ }
+ }
+ });
+ } catch (Exception e) {
+ if (completed.compareAndSet(false, true)) {
+ receipt.cancel();
+ logger.warn(String.format("[VmNicLifecycle] %s.reconcileOnHost(host=%s) " +
+ "threw exception", ext.getClass().getSimpleName(), hostUuid), e);
+ whileCompletion.done();
+ }
+ }
+ }, extensions.size()).run(new WhileDoneCompletion(completion) {
+ @Override
+ public void done(ErrorCodeList errorCodeList) {
+ completion.done();
+ }
+ });
+ }
+}
diff --git a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsg.java b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsg.java
index 8228f15ae1a..3d77b9d1335 100644
--- a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsg.java
+++ b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsg.java
@@ -44,7 +44,7 @@ public class APIChangeLoadBalancerListenerMsg extends APIMessage implements Load
@APIParam(numberRange = {LoadBalancerConstants.HEALTH_CHECK_INTERVAL_MIN, LoadBalancerConstants.HEALTH_CHECK_INTERVAL_MAX}, required = false)
private Integer healthCheckInterval;
- @APIParam(validValues = {LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_TCP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP}, required = false)
+ @APIParam(validValues = {LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_TCP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE}, required = false)
private String healthCheckProtocol;
@APIParam(validValues = {"GET", "HEAD"}, required = false)
private String healthCheckMethod;
diff --git a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsgDoc_zh_cn.groovy b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsgDoc_zh_cn.groovy
index bbe5abcf884..c92f14186a4 100644
--- a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsgDoc_zh_cn.groovy
+++ b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APIChangeLoadBalancerListenerMsgDoc_zh_cn.groovy
@@ -102,7 +102,7 @@ doc {
type "String"
optional true
since "3.9"
- values ("tcp","udp","http")
+ values ("tcp","udp","http","none")
}
column {
name "healthCheckMethod"
diff --git a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsg.java b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsg.java
index 0b123054386..5b495571cc9 100755
--- a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsg.java
+++ b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsg.java
@@ -39,7 +39,7 @@ public class APICreateLoadBalancerListenerMsg extends APICreateMessage implement
private String protocol;
@APIParam(resourceType = CertificateVO.class, required = false)
private String certificateUuid;
- @APIParam(validValues = {LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_TCP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP}, required = false)
+ @APIParam(validValues = {LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_TCP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP, LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE}, required = false)
private String healthCheckProtocol;
@APIParam(validValues = {"GET", "HEAD"}, required = false)
private String healthCheckMethod;
diff --git a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsgDoc_zh_cn.groovy b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsgDoc_zh_cn.groovy
index cfaae8626bb..9496c068530 100644
--- a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsgDoc_zh_cn.groovy
+++ b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/APICreateLoadBalancerListenerMsgDoc_zh_cn.groovy
@@ -93,7 +93,7 @@ doc {
type "String"
optional true
since "3.9"
- values ("tcp","udp","http")
+ values ("tcp","udp","http","none")
}
column {
name "healthCheckMethod"
diff --git a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java
index f2f8271674b..b846b54185e 100755
--- a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java
+++ b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java
@@ -695,6 +695,21 @@ private boolean hasTag(APIMessage msg, PatternedSystemTag tag) {
return false;
}
+ private List getSystemTagTokens(APIMessage msg, PatternedSystemTag tag, String token) {
+ List tokens = new ArrayList<>();
+ if (msg.getSystemTags() == null) {
+ return tokens;
+ }
+
+ for (String t : msg.getSystemTags()) {
+ if (tag.isMatch(t)) {
+ tokens.add(tag.getTokenByTag(t, token));
+ }
+ }
+
+ return tokens;
+ }
+
private void insertTagIfNotExisting(APICreateMessage msg, PatternedSystemTag tag, String value) {
if (!hasTag(msg, tag)) {
msg.addSystemTag(value);
@@ -713,6 +728,47 @@ private Boolean verifyHttpCode(String httpCode) {
});
}
+ private boolean isHealthCheckProtocolNotSupportedByListenerProtocol(String listenerProtocol, String healthCheckProtocol) {
+ boolean isUdpListener = LoadBalancerConstants.LB_PROTOCOL_UDP.equals(listenerProtocol);
+ boolean isUdpHealthCheck = LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP.equals(healthCheckProtocol);
+ boolean isNoneHealthCheck = LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE.equals(healthCheckProtocol);
+
+ if (isUdpListener) {
+ return !(isUdpHealthCheck || isNoneHealthCheck);
+ }
+
+ return isUdpHealthCheck || isNoneHealthCheck;
+ }
+
+ private String getHealthCheckProtocolFromTarget(String healthCheckTarget) {
+ if (healthCheckTarget == null) {
+ return null;
+ }
+
+ String[] ts = healthCheckTarget.split(":");
+ return ts.length == 2 ? ts[0] : null;
+ }
+
+ private String getHealthCheckPortFromTarget(String healthCheckTarget) {
+ if (healthCheckTarget == null) {
+ return null;
+ }
+
+ String[] ts = healthCheckTarget.split(":");
+ return ts.length == 2 ? ts[1] : null;
+ }
+
+ private boolean isNoneHealthCheckTargetWithSpecificPort(String healthCheckTarget) {
+ if (healthCheckTarget == null) {
+ return false;
+ }
+
+ String[] ts = healthCheckTarget.split(":");
+ return ts.length == 2 &&
+ LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE.equals(ts[0]) &&
+ !"default".equals(ts[1]);
+ }
+
private void validate(APICreateLoadBalancerListenerMsg msg) {
LoadBalancerVO lbVO = dbf.findByUuid(msg.getLoadBalancerUuid(), LoadBalancerVO.class);
@@ -730,34 +786,57 @@ private void validate(APICreateLoadBalancerListenerMsg msg) {
msg.getProtocol(), msg.getHealthCheckProtocol()));
}
}
+
+ List healthCheckTargets = getSystemTagTokens(msg, LoadBalancerSystemTags.HEALTH_TARGET,
+ LoadBalancerSystemTags.HEALTH_TARGET_TOKEN);
+ if (healthCheckTargets.size() > 1) {
+ throw new ApiMessageInterceptionException(
+ operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10177, "only one health check target is allowed, but got %s",
+ healthCheckTargets));
+ }
+ String healthCheckTarget = healthCheckTargets.isEmpty() ? null : healthCheckTargets.get(0);
+ if (isNoneHealthCheckTargetWithSpecificPort(healthCheckTarget)) {
+ throw new ApiMessageInterceptionException(
+ operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10178, "the health check target [%s] is invalid, health check protocol none only supports default target",
+ healthCheckTarget));
+ }
+ String healthCheckProtocolInTarget = getHealthCheckProtocolFromTarget(healthCheckTarget);
+
if (msg.getHealthCheckProtocol() == null) {
- if (LoadBalancerConstants.LB_PROTOCOL_UDP.equals(msg.getProtocol())) {
+ if (healthCheckProtocolInTarget != null) {
+ msg.setHealthCheckProtocol(healthCheckProtocolInTarget);
+ } else if (LoadBalancerConstants.LB_PROTOCOL_UDP.equals(msg.getProtocol())) {
msg.setHealthCheckProtocol(LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP);
} else {
msg.setHealthCheckProtocol(LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_TCP);
}
} else {
- if (LoadBalancerConstants.LB_PROTOCOL_UDP.equals(msg.getProtocol()) && !LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP.equals(msg.getHealthCheckProtocol()) ||
- !LoadBalancerConstants.LB_PROTOCOL_UDP.equals(msg.getProtocol()) && LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP.equals(msg.getHealthCheckProtocol())) {
+ if (healthCheckProtocolInTarget != null && !healthCheckProtocolInTarget.equals(msg.getHealthCheckProtocol())) {
throw new ApiMessageInterceptionException(
- operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10062, "the listener with protocol [%s] doesn't support this health check:[%s]",
- msg.getProtocol(), msg.getHealthCheckProtocol()));
+ operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10179, "the health check protocol [%s] conflicts with health check target [%s]",
+ msg.getHealthCheckProtocol(), healthCheckTarget));
}
- if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP.equals(msg.getHealthCheckProtocol())) {
- if (msg.getHealthCheckURI() == null) {
- throw new ApiMessageInterceptionException(
- operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10063, "the http health check protocol must be specified its healthy checking parameter healthCheckURI"));
- }
+ }
- if (msg.getHealthCheckMethod() == null) {
- msg.setHealthCheckMethod(LoadBalancerConstants.HealthCheckMothod.HEAD.toString());
- }
- }
- if (msg.getHealthCheckHttpCode() != null && !verifyHttpCode(msg.getHealthCheckHttpCode())) {
+ if (isHealthCheckProtocolNotSupportedByListenerProtocol(msg.getProtocol(), msg.getHealthCheckProtocol())) {
+ throw new ApiMessageInterceptionException(
+ operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10062, "the listener with protocol [%s] doesn't support this health check:[%s]",
+ msg.getProtocol(), msg.getHealthCheckProtocol()));
+ }
+ if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP.equals(msg.getHealthCheckProtocol())) {
+ if (msg.getHealthCheckURI() == null) {
throw new ApiMessageInterceptionException(
- operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10064, "the http health check protocol's expecting code [%s] is invalidate", msg.getHealthCheckHttpCode()));
+ operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10063, "the http health check protocol must be specified its healthy checking parameter healthCheckURI"));
+ }
+
+ if (msg.getHealthCheckMethod() == null) {
+ msg.setHealthCheckMethod(LoadBalancerConstants.HealthCheckMothod.HEAD.toString());
}
}
+ if (msg.getHealthCheckHttpCode() != null && !verifyHttpCode(msg.getHealthCheckHttpCode())) {
+ throw new ApiMessageInterceptionException(
+ operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10064, "the http health check protocol's expecting code [%s] is invalidate", msg.getHealthCheckHttpCode()));
+ }
if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP.equals(msg.getHealthCheckProtocol())) {
String expectResult = LoadBalancerConstants.HealthCheckStatusCode.http_2xx.toString();
@@ -1384,21 +1463,40 @@ private void validate(APIChangeLoadBalancerListenerMsg msg) {
throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10118, "the listener with protocol [%s] doesn't support select security policy", listenerVO.getProtocol(), msg.getHealthCheckProtocol()));
}
}
- if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP.equals(msg.getHealthCheckProtocol())) {
- String healthTarget = LoadBalancerSystemTags.HEALTH_TARGET.getTokenByResourceUuid(msg.getLoadBalancerListenerUuid(),
- LoadBalancerSystemTags.HEALTH_TARGET_TOKEN);
- String[] ts = healthTarget.split(":");
- if (ts.length != 2) {
- throw new OperationFailureException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10119, "invalid health target[%s], the format is targetCheckProtocol:port, for example, tcp:default", target));
- }
+ String healthTarget = LoadBalancerSystemTags.HEALTH_TARGET.getTokenByResourceUuid(msg.getLoadBalancerListenerUuid(),
+ LoadBalancerSystemTags.HEALTH_TARGET_TOKEN);
+ String currentHealthCheckProtocol = getHealthCheckProtocolFromTarget(healthTarget);
+ String currentHealthCheckTarget = getHealthCheckPortFromTarget(healthTarget);
+ String effectiveHealthCheckProtocol = msg.getHealthCheckProtocol() == null ? currentHealthCheckProtocol : msg.getHealthCheckProtocol();
+ String effectiveHealthCheckTarget;
+ if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE.equals(msg.getHealthCheckProtocol()) &&
+ msg.getHealthCheckTarget() == null) {
+ effectiveHealthCheckTarget = LoadBalancerConstants.HEALTH_CHECK_TARGET_DEFAULT;
+ } else {
+ effectiveHealthCheckTarget = msg.getHealthCheckTarget() == null ? currentHealthCheckTarget : msg.getHealthCheckTarget();
+ }
+ if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE.equals(effectiveHealthCheckProtocol) &&
+ !LoadBalancerConstants.HEALTH_CHECK_TARGET_DEFAULT.equals(effectiveHealthCheckTarget)) {
+ throw new ApiMessageInterceptionException(
+ operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10178, "the health check target [%s] is invalid, health check protocol none only supports default target",
+ effectiveHealthCheckTarget));
+ }
- if (LoadBalancerConstants.LB_PROTOCOL_UDP.equals(listenerVO.getProtocol()) && !LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP.equals(msg.getHealthCheckProtocol()) ||
- !LoadBalancerConstants.LB_PROTOCOL_UDP.equals(listenerVO.getProtocol()) && LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_UDP.equals(msg.getHealthCheckProtocol())) {
+ if (msg.getHealthCheckProtocol() != null) {
+ if (isHealthCheckProtocolNotSupportedByListenerProtocol(listenerVO.getProtocol(), msg.getHealthCheckProtocol())) {
throw new ApiMessageInterceptionException(
operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10120, "the listener with protocol [%s] doesn't support this health check:[%s]",
listenerVO.getProtocol(), msg.getHealthCheckProtocol()));
}
+ }
+
+ if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_HTTP.equals(msg.getHealthCheckProtocol())) {
+ String[] ts = healthTarget.split(":");
+ if (ts.length != 2) {
+ throw new OperationFailureException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10119, "invalid health target[%s], the format is targetCheckProtocol:port, for example, tcp:default", target));
+ }
+
if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_TCP.equals(ts[0]) && (msg.getHealthCheckMethod() == null || msg.getHealthCheckURI() == null)) {
throw new ApiMessageInterceptionException(
operr(ORG_ZSTACK_NETWORK_SERVICE_LB_10121, "the http health check protocol must be specified its healthy checking parameters including healthCheckMethod and healthCheckURI"));
diff --git a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
index dadec74b6ec..e219f24eb8b 100755
--- a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
+++ b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerBase.java
@@ -2406,9 +2406,10 @@ public void run(SyncTaskChain chain) {
LoadBalancerSystemTags.HEALTH_PARAMETER.delete(msg.getUuid());
}
+ String target = LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE.equals(msg.getHealthCheckProtocol()) ? "default" : ts[1];
LoadBalancerSystemTags.HEALTH_TARGET.update(msg.getUuid(),
LoadBalancerSystemTags.HEALTH_TARGET.instantiateTag(map(
- e(LoadBalancerSystemTags.HEALTH_TARGET_TOKEN, String.format("%s:%s", msg.getHealthCheckProtocol(), ts[1])))
+ e(LoadBalancerSystemTags.HEALTH_TARGET_TOKEN, String.format("%s:%s", msg.getHealthCheckProtocol(), target)))
));
ts = getHeathCheckTarget(msg.getUuid());
}
diff --git a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerConstants.java b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerConstants.java
index fe147c11cf4..f48b53c930b 100755
--- a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerConstants.java
+++ b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerConstants.java
@@ -29,6 +29,7 @@ public class LoadBalancerConstants {
public static final String HEALTH_CHECK_TARGET_PROTOCL_TCP = "tcp";
public static final String HEALTH_CHECK_TARGET_PROTOCL_UDP = "udp";
public static final String HEALTH_CHECK_TARGET_PROTOCL_HTTP = "http";
+ public static final String HEALTH_CHECK_TARGET_PROTOCL_NONE = "none";
public static final String HTTP_MODE_HTTP_KEEP_ALIVE = "http-keep-alive";
public static final String HTTP_MODE_HTTP_SERVER_CLOSE = "http-server-close";
@@ -131,6 +132,7 @@ public String toString() {
HEALTH_CHECK_TARGET_PROTOCOLS.add(HEALTH_CHECK_TARGET_PROTOCL_TCP);
HEALTH_CHECK_TARGET_PROTOCOLS.add(HEALTH_CHECK_TARGET_PROTOCL_UDP);
HEALTH_CHECK_TARGET_PROTOCOLS.add(HEALTH_CHECK_TARGET_PROTOCL_HTTP);
+ HEALTH_CHECK_TARGET_PROTOCOLS.add(HEALTH_CHECK_TARGET_PROTOCL_NONE);
HTTP_MODES.add(HTTP_MODE_HTTP_KEEP_ALIVE);
HTTP_MODES.add(HTTP_MODE_HTTP_SERVER_CLOSE);
diff --git a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
index 8f1f8e1fc62..6b4da585104 100755
--- a/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
+++ b/plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerManagerImpl.java
@@ -732,6 +732,20 @@ public void validateSystemTag(String resourceUuid, Class resourceType, String sy
}
String port = ts[1];
+ if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE.equals(protocol) && !"default".equals(port)) {
+ throw new OperationFailureException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10014, "invalid health target[%s], health check protocol none only supports default target", systemTag));
+ }
+
+ LoadBalancerListenerVO listener = Q.New(LoadBalancerListenerVO.class)
+ .eq(LoadBalancerListenerVO_.uuid, resourceUuid)
+ .find();
+ if (listener != null) {
+ if (LoadBalancerConstants.HEALTH_CHECK_TARGET_PROTOCL_NONE.equals(protocol) &&
+ !LoadBalancerConstants.LB_PROTOCOL_UDP.equals(listener.getProtocol())) {
+ throw new OperationFailureException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10014, "invalid health target[%s], health check protocol none is only supported by udp listener", systemTag));
+ }
+ }
+
if (!"default".equals(port)) {
try {
int p = Integer.parseInt(port);
diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/AbstractSdnControllerFactory.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/AbstractSdnControllerFactory.java
new file mode 100644
index 00000000000..095632acd68
--- /dev/null
+++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/AbstractSdnControllerFactory.java
@@ -0,0 +1,45 @@
+package org.zstack.sdnController;
+
+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.cloudbus.EventFacade;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.core.db.SQL;
+import org.zstack.header.network.sdncontroller.*;
+import org.zstack.sdnController.header.SdnControllerCanonicalEvents;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+
+@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
+public abstract class AbstractSdnControllerFactory implements SdnControllerFactory {
+ private static final CLogger logger = Utils.getLogger(AbstractSdnControllerFactory.class);
+
+ @Autowired
+ private DatabaseFacade factoryDbf;
+ @Autowired
+ private EventFacade evtf;
+
+ @Override
+ public void changeSdnControllerStatus(SdnControllerVO vo, SdnControllerStatusEvent event) {
+ SdnControllerStatus newStatus = resolveStatus(event, vo);
+ if (newStatus == vo.getStatus()) {
+ return;
+ }
+ SdnControllerStatus oldStatus = vo.getStatus();
+ logger.debug(String.format("sdn controller[%s] event[%s]: %s -> %s",
+ vo.getUuid(), event, oldStatus, newStatus));
+ SQL.New(SdnControllerVO.class)
+ .eq(SdnControllerVO_.uuid, vo.getUuid())
+ .set(SdnControllerVO_.status, newStatus)
+ .update();
+ vo.setStatus(newStatus);
+ SdnControllerCanonicalEvents.SdnControllerStatusChangedData d = new SdnControllerCanonicalEvents.SdnControllerStatusChangedData();
+ d.setSdnControllerUuid(vo.getUuid());
+ d.setSdnControllerType(vo.getVendorType());
+ d.setOldStatus(oldStatus.toString());
+ d.setNewStatus(newStatus.toString());
+ d.setInv(SdnControllerInventory.valueOf(vo));
+ evtf.fire(SdnControllerCanonicalEvents.SDNCONTROLLER_STATUS_CHANGED_PATH, d);
+ }
+}
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..848c962b2e3 100644
--- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java
+++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerBase.java
@@ -11,7 +11,6 @@
import org.zstack.core.cascade.CascadeFacade;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
-import org.zstack.core.cloudbus.EventFacade;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.Q;
@@ -69,8 +68,6 @@ public class SdnControllerBase {
@Autowired
private ThreadFacade thdf;
@Autowired
- private EventFacade evtf;
- @Autowired
SdnControllerManager sdnMgr;
@Autowired
private PluginRegistry pluginRgty;
@@ -154,6 +151,8 @@ public void run(SyncTaskChain chain) {
flowChain.getData().put(SDN_CONTROLLER_UUID, self.getUuid());
flowChain.setName(String.format("sync-sdn-controller-data-%s-%s", self.getUuid(), self.getName()));
+ // allowEmptyFlow: vendors may not provide sync flows; treat empty chain as success
+ flowChain.allowEmptyFlow();
// Start the chain; flows in factory-provided chain should perform data sync operations
flowChain.done(new FlowDoneHandler(msg) {
@Override
@@ -179,26 +178,6 @@ public String getName() {
});
}
- public void changeSdnControllerStatus(SdnControllerStatus status) {
- if (status == self.getStatus()) {
- return;
- }
-
- SdnControllerStatus oldStatus = self.getStatus();
- logger.debug(String.format("sdn controller [%s] changed status, old status: [%s], new status: [%s]",
- self.getUuid(), oldStatus, status.toString()));
- self.setStatus(status);
- self = dbf.updateAndRefresh(self);
-
- SdnControllerCanonicalEvents.SdnControllerStatusChangedData d = new SdnControllerCanonicalEvents.SdnControllerStatusChangedData();
- d.setSdnControllerUuid(self.getUuid());
- d.setSdnControllerType(self.getVendorType());
- d.setOldStatus(oldStatus.toString());
- d.setNewStatus(status.toString());
- d.setInv(SdnControllerInventory.valueOf(self));
- evtf.fire(SdnControllerCanonicalEvents.SDNCONTROLLER_STATUS_CHANGED_PATH, d);
- }
-
private void doChangeSdnController(APIChangeSdnControllerMsg msg, Completion completion) {
FlowChain chain = FlowChainBuilder.newSimpleFlowChain();
chain.setName(String.format("change-sdn-controller-%s-%s", self.getUuid(), self.getName()));
@@ -224,6 +203,12 @@ public void run(FlowTrigger trigger, Map data) {
}
if (changed) {
+ String newUsername = self.getUsername();
+ String newPassword = self.getPassword();
+ // Keep concurrent DB changes while applying the credential update.
+ self = dbf.reload(self);
+ self.setUsername(newUsername);
+ self.setPassword(newPassword);
self = dbf.updateAndRefresh(self);
chain.getData().put(SDN_CONTROLLER_CHANGED, changed);
}
@@ -257,6 +242,11 @@ public void rollback(FlowRollback trigger, Map data) {
self.setUsername(username);
}
if (password != null || username != null) {
+ String rollbackUsername = self.getUsername();
+ String rollbackPassword = self.getPassword();
+ self = dbf.reload(self);
+ self.setUsername(rollbackUsername);
+ self.setPassword(rollbackPassword);
self = dbf.updateAndRefresh(self);
}
@@ -422,13 +412,13 @@ private void doReconnectSdnController(Completion completion) {
@Override
public void run(FlowTrigger trigger, Map data) {
- changeSdnControllerStatus(SdnControllerStatus.Connecting);
+ sdnMgr.getSdnControllerFactory(self.getVendorType()).changeSdnControllerStatus(self, SdnControllerStatusEvent.RECONNECT_STARTED);
trigger.next();
}
@Override
public void rollback(FlowRollback trigger, Map data) {
- changeSdnControllerStatus(SdnControllerStatus.Disconnected);
+ sdnMgr.getSdnControllerFactory(self.getVendorType()).changeSdnControllerStatus(self, SdnControllerStatusEvent.RECONNECT_FAILED);
trigger.rollback();
}
}).then(new NoRollbackFlow() {
@@ -454,7 +444,7 @@ public void fail(ErrorCode errorCode) {
@Override
public void run(FlowTrigger trigger, Map data) {
- changeSdnControllerStatus(SdnControllerStatus.Connected);
+ sdnMgr.getSdnControllerFactory(self.getVendorType()).changeSdnControllerStatus(self, SdnControllerStatusEvent.RECONNECT_SUCCESS);
trigger.next();
}
}).done(new FlowDoneHandler(completion) {
@@ -903,7 +893,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..9455cf2727c 100644
--- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerFactory.java
+++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerFactory.java
@@ -4,13 +4,28 @@
import org.zstack.header.core.workflow.FlowChain;
import org.zstack.header.network.l3.SdnControllerL3;
import org.zstack.header.network.service.SdnControllerDhcp;
-import org.zstack.network.securitygroup.SecurityGroupSdnBackend;
+import org.zstack.header.network.sdncontroller.SdnControllerStatus;
+import org.zstack.header.network.sdncontroller.SdnControllerStatusEvent;
import org.zstack.header.network.sdncontroller.SdnControllerVO;
+import org.zstack.network.securitygroup.SecurityGroupSdnBackend;
public interface SdnControllerFactory {
SdnControllerType getVendorType();
- SdnControllerVO persistSdnController(SdnControllerVO vo);
+ default SdnControllerStatus resolveStatus(SdnControllerStatusEvent event, SdnControllerVO vo) {
+ switch (event) {
+ case RECONNECT_STARTED: return SdnControllerStatus.Connecting;
+ case RECONNECT_SUCCESS: return SdnControllerStatus.Connected;
+ case RECONNECT_FAILED: return SdnControllerStatus.Disconnected;
+ case PING_FAILED: return SdnControllerStatus.Disconnected;
+ case INIT_SYNC_STARTED: return SdnControllerStatus.Connecting;
+ case INIT_SYNC_SUCCESS: return SdnControllerStatus.Connected;
+ case INIT_SYNC_FAILED: return SdnControllerStatus.Disconnected;
+ default: return vo.getStatus();
+ }
+ }
+
+ void changeSdnControllerStatus(SdnControllerVO vo, SdnControllerStatusEvent event);
SdnController getSdnController(SdnControllerVO vo);
diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java
index 502efb5f21f..4a63f3c5c2b 100644
--- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java
+++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerL2.java
@@ -38,6 +38,7 @@ public interface SdnControllerL2 {
default void addVmNics(List nics, Completion completion) {completion.success();};
default void removeVmNics(List nics, Completion completion) {completion.success();};
+ default void releaseNicIps(List nics, Completion completion) {completion.success();};
default void addL3NetworkIpRange(L3NetworkInventory inv, IpRangeInventory ipr, Completion completion) {completion.success();};
default void deleteL3NetworkIpRange(L3NetworkInventory inv, IpRangeInventory ipr, Completion completion) {completion.success();};
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..e832f146738 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,26 @@ 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 {
-
+ /**
+ * 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
- 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);
+ private Set getNewlyAllocatedNicUuidsForStart(VmInstanceSpec spec) {
+ if (spec.getCurrentVmOperation() != VmInstanceConstant.VmOperation.Start) {
+ return null;
}
- if (nicMaps.isEmpty()) {
- completion.success();
- return;
+ List nicUuids = spec.getExtensionData(SdnControllerConstant.ALLOCATED_IPS_ON_START, List.class);
+ if (nicUuids == null || nicUuids.isEmpty()) {
+ return Collections.emptySet();
}
- removeLogicalPort(nicMaps, completion);
+ return new HashSet<>(nicUuids);
}
@Override
@@ -899,4 +663,207 @@ 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;
+ }
+
+ Set newlyAllocatedNicUuids = getNewlyAllocatedNicUuidsForStart(spec);
+ 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;
+ }
+
+ if (newlyAllocatedNicUuids != null
+ && L2NetworkConstant.VSWITCH_TYPE_OVN_DPDK.equals(l2VO.getvSwitchType())
+ && !newlyAllocatedNicUuids.contains(nic.getUuid())) {
+ 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);
+ }
+
+ @Override
+ public void releaseNicIps(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;
+ }
+
+ releaseNicIpsFromPort(nicMaps, completion);
+ }
+
+ private void releaseNicIpsFromPort(Map> nicMaps, Completion completion) {
+ new While<>(nicMaps.entrySet()).each((e, wcomp) -> {
+ SdnControllerVO vo = dbf.findByUuid(e.getKey(), SdnControllerVO.class);
+ if (vo == null) {
+ wcomp.addError(operr(ORG_ZSTACK_SDNCONTROLLER_10031,
+ "cannot release SDN NIC IPs because sdn controller[uuid:%s] cannot be found", e.getKey()));
+ wcomp.allDone();
+ return;
+ }
+ SdnControllerFactory factory;
+ try {
+ factory = getSdnControllerFactory(vo.getVendorType());
+ } catch (CloudRuntimeException ex) {
+ wcomp.addError(operr(ORG_ZSTACK_SDNCONTROLLER_10032,
+ "cannot release SDN NIC IPs because sdn controller factory[type:%s] cannot be found: %s",
+ vo.getVendorType(), ex.getMessage()));
+ wcomp.allDone();
+ return;
+ }
+ SdnControllerL2 controller = factory.getSdnControllerL2(vo);
+ if (controller == null) {
+ wcomp.addError(operr(ORG_ZSTACK_SDNCONTROLLER_10033,
+ "cannot release SDN NIC IPs because sdn controller L2[controllerUuid:%s, type:%s] cannot be found",
+ vo.getUuid(), vo.getVendorType()));
+ wcomp.allDone();
+ return;
+ }
+ controller.releaseNicIps(e.getValue(), 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));
+ }
+ }
+ });
+ }
}
diff --git a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerPingTracker.java b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerPingTracker.java
index a959dee3005..d52d26909be 100644
--- a/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerPingTracker.java
+++ b/plugin/sdnController/src/main/java/org/zstack/sdnController/SdnControllerPingTracker.java
@@ -18,6 +18,7 @@
import org.zstack.header.network.l2.SdnControllerDeleteExtensionPoint;
import org.zstack.header.network.sdncontroller.SdnControllerConstant;
import org.zstack.header.network.sdncontroller.SdnControllerStatus;
+import org.zstack.header.network.sdncontroller.SdnControllerStatusEvent;
import org.zstack.header.network.sdncontroller.SdnControllerVO;
import org.zstack.header.network.sdncontroller.SdnControllerVO_;
import org.zstack.sdnController.header.*;
@@ -53,6 +54,11 @@ public String getResourceName() {
@Override
public NeedReplyMessage getPingMessage(String resUuid) {
SdnControllerVO vo = dbf.findByUuid(resUuid, SdnControllerVO.class);
+ if (vo == null) {
+ logger.warn(String.format("SDN controller[uuid:%s] has been deleted, skip ping sending", resUuid));
+ untrack(resUuid);
+ return null;
+ }
if (vo.getStatus() == SdnControllerStatus.Connecting) {
return null;
}
@@ -81,10 +87,9 @@ public void handleReply(final String resourceUuid, MessageReply reply) {
return;
}
-
if (!reply.isSuccess()) {
logger.warn(String.format("[SDN Ping Tracker]: unable to ping the sdn controller[uuid: %s], %s", resourceUuid, reply.getError()));
- new SdnControllerBase(vo).changeSdnControllerStatus(SdnControllerStatus.Disconnected);
+ sdnMgr.getSdnControllerFactory(vo.getVendorType()).changeSdnControllerStatus(vo, SdnControllerStatusEvent.PING_FAILED);
return;
}
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..75864392a31 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
@@ -5,11 +5,10 @@
import org.zstack.header.network.sdncontroller.SdnControllerConstant;
import org.zstack.header.network.sdncontroller.SdnControllerVO;
import org.zstack.network.securitygroup.SecurityGroupSdnBackend;
-import org.zstack.sdnController.SdnController;
-import org.zstack.sdnController.SdnControllerFactory;
+import org.zstack.sdnController.*;
import org.zstack.sdnController.*;
-public class H3cVcfcSdnControllerFactory implements SdnControllerFactory {
+public class H3cVcfcSdnControllerFactory extends AbstractSdnControllerFactory {
SdnControllerType sdnControllerType = new SdnControllerType(SdnControllerConstant.H3C_VCFC_CONTROLLER);
@Autowired
@@ -20,12 +19,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..97dde1b0725 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
@@ -3,13 +3,14 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.network.securitygroup.SecurityGroupSdnBackend;
+import org.zstack.sdnController.AbstractSdnControllerFactory;
import org.zstack.sdnController.SdnController;
import org.zstack.sdnController.SdnControllerFactory;
import org.zstack.sdnController.SdnControllerL2;
import org.zstack.sdnController.SdnControllerType;
import org.zstack.header.network.sdncontroller.SdnControllerVO;
-public class SugonSdnControllerFactory implements SdnControllerFactory {
+public class SugonSdnControllerFactory extends AbstractSdnControllerFactory {
SdnControllerType sdnControllerType = new SdnControllerType(SugonSdnControllerConstant.TF_CONTROLLER);
@@ -21,11 +22,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/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/ReconnectVirtualRouterVmMsg.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/ReconnectVirtualRouterVmMsg.java
index a0e0d182f18..1685900181d 100755
--- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/ReconnectVirtualRouterVmMsg.java
+++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/ReconnectVirtualRouterVmMsg.java
@@ -9,6 +9,7 @@
public class ReconnectVirtualRouterVmMsg extends NeedReplyMessage implements VmInstanceMessage {
private String virtualRouterVmUuid;
private boolean statusChange = false;
+ private boolean skipGrayscaleUpgradeCheck = false;
public String getVirtualRouterVmUuid() {
@@ -31,4 +32,12 @@ public boolean isStatusChange() {
public void setStatusChange(boolean statusChange) {
this.statusChange = statusChange;
}
+
+ public boolean isSkipGrayscaleUpgradeCheck() {
+ return skipGrayscaleUpgradeCheck;
+ }
+
+ public void setSkipGrayscaleUpgradeCheck(boolean skipGrayscaleUpgradeCheck) {
+ this.skipGrayscaleUpgradeCheck = skipGrayscaleUpgradeCheck;
+ }
}
diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouter.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouter.java
index de4468664dd..85a275a84e0 100755
--- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouter.java
+++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouter.java
@@ -68,6 +68,16 @@
public class VirtualRouter extends ApplianceVmBase {
private static final CLogger logger = Utils.getLogger(VirtualRouter.class);
+ private enum GrayscaleUpgradeCheck {
+ REQUIRED,
+ SKIPPED
+ }
+
+ private enum ReconnectSource {
+ INTERNAL,
+ API
+ }
+
static {
allowedOperations.addState(VmInstanceState.Running, APIReconnectVirtualRouterMsg.class.getName());
allowedOperations.addState(VmInstanceState.Running, APIProvisionVirtualRouterConfigMsg.class.getName());
@@ -608,7 +618,7 @@ public String getSyncSignature() {
public void run(final SyncTaskChain chain) {
final ReconnectVirtualRouterVmReply reply = new ReconnectVirtualRouterVmReply();
- if (upgradeChecker.skipInnerDeployOrInitOnCurrentAgent(self.getUuid())) {
+ if (!msg.isSkipGrayscaleUpgradeCheck() && upgradeChecker.skipInnerDeployOrInitOnCurrentAgent(self.getUuid())) {
bus.reply(msg, reply);
chain.next();
return;
@@ -626,7 +636,7 @@ public void run(final SyncTaskChain chain) {
return;
}
- reconnect(new Completion(msg, chain) {
+ Completion completion = new Completion(msg, chain) {
@Override
public void success() {
bus.reply(msg, reply);
@@ -639,7 +649,13 @@ public void fail(ErrorCode errorCode) {
bus.reply(msg, reply);
chain.next();
}
- });
+ };
+
+ if (msg.isSkipGrayscaleUpgradeCheck()) {
+ reconnectWithoutGrayscaleUpgradeCheck(completion);
+ } else {
+ reconnect(completion);
+ }
}
@Override
@@ -849,7 +865,7 @@ public void run(final SyncTaskChain chain) {
return;
}
- reconnect(true, new Completion(msg, chain) {
+ reconnectFromApi(new Completion(msg, chain) {
@Override
public void success() {
evt.setInventory((ApplianceVmInventory) getSelfInventory());
@@ -874,10 +890,18 @@ public String getName() {
}
private void reconnect(final Completion completion) {
- reconnect(false, completion);
+ reconnect(ReconnectSource.INTERNAL, GrayscaleUpgradeCheck.REQUIRED, completion);
+ }
+
+ private void reconnectFromApi(final Completion completion) {
+ reconnect(ReconnectSource.API, GrayscaleUpgradeCheck.REQUIRED, completion);
+ }
+
+ private void reconnectWithoutGrayscaleUpgradeCheck(final Completion completion) {
+ reconnect(ReconnectSource.INTERNAL, GrayscaleUpgradeCheck.SKIPPED, completion);
}
- private void reconnect(Boolean fromApi, final Completion completion) {
+ private void reconnect(ReconnectSource source, GrayscaleUpgradeCheck grayscaleUpgradeCheck, final Completion completion) {
ApplianceVmStatus oldStatus = getSelf().getStatus();
FlowChain chain = getReconnectChain();
@@ -887,7 +911,8 @@ private void reconnect(Boolean fromApi, final Completion completion) {
chain.getData().put(Params.isReconnect.toString(), Boolean.TRUE.toString());
chain.getData().put(Params.managementNicIp.toString(), vr.getManagementNic().getIp());
chain.getData().put(Params.applianceVmUuid.toString(), self.getUuid());
- chain.getData().put(Params.fromApi.toString(), fromApi.toString());
+ chain.getData().put(Params.fromApi.toString(), Boolean.toString(source == ReconnectSource.API));
+ chain.getData().put(Params.skipGrayscaleUpgradeCheck.toString(), Boolean.toString(grayscaleUpgradeCheck == GrayscaleUpgradeCheck.SKIPPED));
SimpleQuery q = dbf.createQuery(ApplianceVmFirewallRuleVO.class);
q.add(ApplianceVmFirewallRuleVO_.applianceVmUuid, Op.EQ, getSelf().getUuid());
diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/portforwarding/PortForwardingConfigProxy.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/portforwarding/PortForwardingConfigProxy.java
index feff1ea1f01..2a1b8dd4920 100644
--- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/portforwarding/PortForwardingConfigProxy.java
+++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/portforwarding/PortForwardingConfigProxy.java
@@ -35,6 +35,9 @@ protected void attachNetworkServiceToNoHaVirtualRouter(String vrUuid, String typ
@Override
protected void detachNetworkServiceFromNoHaVirtualRouter(String vrUuid, String type, List serviceUuids) {
+ if (serviceUuids == null || serviceUuids.isEmpty()) {
+ return;
+ }
SQL.New(VirtualRouterPortForwardingRuleRefVO.class).eq(VirtualRouterPortForwardingRuleRefVO_.virtualRouterVmUuid, vrUuid)
.in(VirtualRouterPortForwardingRuleRefVO_.uuid, serviceUuids).delete();
}
diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/vyos/VyosDeployAgentFlow.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/vyos/VyosDeployAgentFlow.java
index 163fd4d22d2..a6b8c6386b3 100755
--- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/vyos/VyosDeployAgentFlow.java
+++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/vyos/VyosDeployAgentFlow.java
@@ -95,8 +95,9 @@ public void run(FlowTrigger trigger, Map data) {
}
boolean fromApi = Boolean.parseBoolean(String.valueOf(data.get(Params.fromApi.toString())));
+ boolean skipGrayscaleUpgradeCheck = Boolean.parseBoolean(String.valueOf(data.get(Params.skipGrayscaleUpgradeCheck.toString())));
boolean isReconnect = Boolean.parseBoolean(String.valueOf(data.get(Params.isReconnect.toString())));
- if (!fromApi && upgradeChecker.skipInnerDeployOrInitOnCurrentAgent(vrUuid)) {
+ if (!fromApi && !skipGrayscaleUpgradeCheck && upgradeChecker.skipInnerDeployOrInitOnCurrentAgent(vrUuid)) {
trigger.next();
return;
}
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/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java b/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java
index 0626b00ba7a..48a3eed041b 100644
--- a/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java
+++ b/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageController.java
@@ -276,8 +276,9 @@ public void deactivate(String installPath, String protocol, ActiveVolumeClient c
@Override
public void blacklist(String installPath, String protocol, HostInventory h, Completion comp) {
- // todo
- comp.success();
+ throw new OperationFailureException(operr("xinfini does not support volume path isolation yet, " +
+ "abort starting VM on host[uuid:%s, ip:%s] to prevent split-brain on volume[path:%s, protocol:%s]",
+ h.getUuid(), h.getManagementIp(), installPath, protocol));
}
@Override
diff --git a/rest/src/main/resources/scripts/GoApiTemplate.groovy b/rest/src/main/resources/scripts/GoApiTemplate.groovy
index fefe945ba3c..eb207e67ac4 100644
--- a/rest/src/main/resources/scripts/GoApiTemplate.groovy
+++ b/rest/src/main/resources/scripts/GoApiTemplate.groovy
@@ -2,6 +2,8 @@ package scripts
import org.zstack.header.query.APIQueryMessage
import org.zstack.header.rest.RestRequest
+import org.zstack.header.rest.SDK
+import org.zstack.header.rest.RestResponse
import org.zstack.rest.sdk.SdkFile
import org.zstack.rest.sdk.SdkTemplate
import org.zstack.utils.Utils
@@ -20,6 +22,7 @@ class GoApiTemplate implements SdkTemplate {
private RestRequest at
private String path
private Class responseClass
+ private String allTo;
private String replyName
private SdkTemplate inventoryGenerator
@@ -54,6 +57,9 @@ class GoApiTemplate implements SdkTemplate {
// Track APIs that should be skipped during generation
private static Set skippedApis = new HashSet<>()
+ // Flag to indicate if the template was successfully initialized
+ private boolean valid = false
+
GoApiTemplate(Class apiMsgClass, SdkTemplate inventoryGenerator) {
try {
apiMsgClazz = apiMsgClass
@@ -85,6 +91,12 @@ class GoApiTemplate implements SdkTemplate {
}
}
+ allTo = ""
+ if (responseClass != null) {
+ RestResponse restResponse = responseClass.getAnnotation(RestResponse)
+ allTo = restResponse != null ? restResponse.allTo() : ""
+ }
+
if (responseClass != null) {
replyName = responseClass.simpleName.replaceAll('^API', '').replaceAll('Reply$', '').replaceAll('Event$', '')
} else {
@@ -103,6 +115,7 @@ class GoApiTemplate implements SdkTemplate {
queryInventoryClass = findInventoryClass()
logger.warn("[GoSDK] Processing API: " + clzName + " -> action=" + actionType + ", resource=" + resourceName + ", response=" + responseClass?.simpleName)
+ valid = true
} catch (Throwable e) {
logger.error("[GoSDK] CRITICAL ERROR constructing GoApiTemplate for ${apiMsgClass.name}: ${e.class.name}: ${e.message}", e)
throw e
@@ -113,6 +126,14 @@ class GoApiTemplate implements SdkTemplate {
return at
}
+ /**
+ * Check if the template was successfully initialized.
+ * Templates without @RestRequest annotation are invalid.
+ */
+ boolean isValid() {
+ return valid
+ }
+
String getActionType() {
return actionType
}
@@ -184,6 +205,21 @@ class GoApiTemplate implements SdkTemplate {
logger.warn("[GoSDK] Registered ${mappings.size()} LongJob mappings")
}
+ /**
+ * Reset all static state for clean re-generation.
+ * Should be called at the beginning of generate() or before each SDK generation run.
+ */
+ static void reset() {
+ generatedParamFiles.clear()
+ generatedActionFiles.clear()
+ generatedViewFiles.clear()
+ knownInventoryClasses = null
+ groupedApiNames.clear()
+ longJobMappings.clear()
+ skippedApis.clear()
+ logger.warn("[GoSDK] Reset all static state")
+ }
+
/**
* Check if current API supports async operation
*/
@@ -373,6 +409,13 @@ class GoApiTemplate implements SdkTemplate {
return "v1/" + path
}
+ private String getApiOptPath(String optPath) {
+ if (optPath.startsWith("/")) {
+ return "v1" + optPath
+ }
+ return "v1/" + optPath
+ }
+
List generate() {
return []
}
@@ -431,8 +474,12 @@ class GoApiTemplate implements SdkTemplate {
// First check HTTP method from annotation, then fall back to actionType-based logic
if (httpMethod == "POST") {
- // POST operations (Create/Add)
- builder.append(generateCreateMethod(apiPath, viewStructName, false, responseStructName, goInventoryFieldName))
+ // POST operations: Delete-via-POST needs special handling
+ if (actionType == "Delete") {
+ builder.append(generateDeleteViaPostMethod(apiPath, responseStructName))
+ } else {
+ builder.append(generateCreateMethod(apiPath, viewStructName, false, responseStructName, goInventoryFieldName))
+ }
} else if (httpMethod == "GET") {
// GET operations (Get/Query)
builder.append(generateGetMethod(apiPath, viewStructName, unwrapForGet, responseStructName, goInventoryFieldName))
@@ -505,56 +552,55 @@ class GoApiTemplate implements SdkTemplate {
private String generateCreateMethod(String apiPath, String viewStructName, boolean unwrap, String responseStructName, String fieldName) {
boolean hasParams = hasApiParams()
-
- if (!hasParams) {
- // No params: don't require user to pass params, use empty map internally
- if (unwrap) {
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
-\tvar resp view.${responseStructName}
-\tif err := cli.Post("${apiPath}", map[string]interface{}{}, &resp); err != nil {
-\t\treturn nil, err
-\t}
-\treturn &resp.${fieldName}, nil
-}
-"""
- } else {
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
-\tresp := view.${viewStructName}{}
-\tif err := cli.Post("${apiPath}", map[string]interface{}{}, &resp); err != nil {
-\t\treturn nil, err
-\t}
-\treturn &resp, nil
-}
-"""
- }
+ def placeholders = extractUrlPlaceholders(apiPath)
+ String pathExpr = placeholders.isEmpty() ? "\"${apiPath}\"" : buildFullPath(placeholders)
+ String placeholderParams = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
+ String methodParams = "ctx context.Context"
+ if (!placeholderParams.isEmpty()) {
+ methodParams = "${methodParams}, ${placeholderParams}"
}
-
- // Has params: require user to pass params
+ if (hasParams) {
+ methodParams = "${methodParams}, params param.${clzName}Param"
+ }
+
+ String bodyExpr = hasParams ? "params" : "map[string]interface{}{}"
+ String responseKey = getPostResponseKey(viewStructName, responseStructName)
+
+ String retViewStructName = viewStructName
+ String respDecl
+ String returnStmt
if (unwrap) {
- return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) {
-\tvar resp view.${responseStructName}
-\tif err := cli.Post("${apiPath}", params, &resp); err != nil {
-\t\treturn nil, err
-\t}
-\treturn &resp.${fieldName}, nil
-}
-"""
+ respDecl = "var resp view.${responseStructName}"
+ returnStmt = "return &resp.${fieldName}, nil"
} else {
- return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) {
-\tresp := view.${viewStructName}{}
-\tif err := cli.Post("${apiPath}", params, &resp); err != nil {
+ respDecl = "resp := view.${viewStructName}{}"
+ returnStmt = "return &resp, nil"
+ }
+
+ return """func (cli *ZSClient) ${clzName}(${methodParams}) (*view.${retViewStructName}, error) {
+\t${respDecl}
+\tif err := cli.PostWithRespKey(ctx, ${pathExpr}, "${responseKey}", ${bodyExpr}, &resp); err != nil {
\t\treturn nil, err
\t}
-\treturn &resp, nil
+\t${returnStmt}
}
"""
+ }
+
+ private String getPostResponseKey(String viewStructName, String responseStructName) {
+ if (allTo != null && !allTo.isEmpty()) {
+ return allTo
+ }
+ if (inventoryFieldName != null && !inventoryFieldName.isEmpty() && viewStructName != responseStructName) {
+ return inventoryFieldName
}
+ return ""
}
private String generateQueryMethod(String apiPath, String viewStructName) {
- return """func (cli *ZSClient) ${clzName}(params *param.QueryParam) ([]view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, params *param.QueryParam) ([]view.${viewStructName}, error) {
\tvar resp []view.${viewStructName}
-\treturn resp, cli.List("${apiPath}", params, &resp)
+\treturn resp, cli.List(ctx, "${apiPath}", params, &resp)
}
"""
}
@@ -566,7 +612,7 @@ class GoApiTemplate implements SdkTemplate {
private String generatePageMethod(String apiPath, String viewStructName) {
String pageMethodName = clzName.replaceFirst('^Query', 'Page')
String varName = resourceName.substring(0, 1).toLowerCase() + resourceName.substring(1)
- if (varName.endsWith("y")) {
+ if (varName.endsWith("y") && varName.length() > 1 && !"aeiou".contains(varName.charAt(varName.length() - 2).toString())) {
varName = varName.substring(0, varName.length() - 1) + "ies"
} else if (!varName.endsWith("s")) {
varName = varName + "s"
@@ -574,9 +620,9 @@ class GoApiTemplate implements SdkTemplate {
return """
// ${pageMethodName} Pagination
-func (cli *ZSClient) ${pageMethodName}(params *param.QueryParam) ([]view.${viewStructName}, int, error) {
+func (cli *ZSClient) ${pageMethodName}(ctx context.Context, params *param.QueryParam) ([]view.${viewStructName}, int, error) {
\tvar ${varName} []view.${viewStructName}
-\ttotal, err := cli.Page("${apiPath}", params, &${varName})
+\ttotal, err := cli.Page(ctx, "${apiPath}", params, &${varName})
\treturn ${varName}, total, err
}
"""
@@ -602,9 +648,9 @@ func (cli *ZSClient) ${pageMethodName}(params *param.QueryParam) ([]view.${viewS
String spec = buildSpecPath(remainingPlaceholders)
return """
-func (cli *ZSClient) ${getMethodName}(${params}) (*view.${viewStructName}, error) {
+func (cli *ZSClient) ${getMethodName}(ctx context.Context, ${params}) (*view.${viewStructName}, error) {
\tvar resp view.${viewStructName}
-\terr := cli.GetWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp)
+\terr := cli.GetWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "${allTo}", nil, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -615,9 +661,9 @@ func (cli *ZSClient) ${getMethodName}(${params}) (*view.${viewStructName}, error
// Standard case: single uuid parameter
return """
-func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, error) {
+func (cli *ZSClient) ${getMethodName}(ctx context.Context, uuid string) (*view.${viewStructName}, error) {
\tvar resp view.${viewStructName}
-\tif err := cli.Get("${cleanPath}", uuid, nil, &resp); err != nil {
+\tif err := cli.Get(ctx, "${cleanPath}", uuid, nil, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp, nil
@@ -639,9 +685,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
if (placeholders.size() == 0) {
// No placeholder: no uuid parameter needed
// Use GetWithRespKey to extract the inventory field
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\tif err := cli.GetWithRespKey("${cleanPath}", "", "inventory", nil, &resp); err != nil {
+\tif err := cli.GetWithRespKey(ctx, "${cleanPath}", "", "inventory", nil, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp.${fieldName}, nil
@@ -649,9 +695,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""
} else {
// Single placeholder: use GetWithRespKey with uuid to extract inventory
- return """func (cli *ZSClient) ${clzName}(uuid string) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\tif err := cli.GetWithRespKey("${cleanPath}", uuid, "inventory", nil, &resp); err != nil {
+\tif err := cli.GetWithRespKey(ctx, "${cleanPath}", uuid, "inventory", nil, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp.${fieldName}, nil
@@ -666,9 +712,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
String spec = buildSpecPath(remainingPlaceholders)
- return """func (cli *ZSClient) ${clzName}(${params}) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\terr := cli.GetWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp)
+\terr := cli.GetWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -677,14 +723,18 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""
}
} else {
+ // Not unwrapping: use responseStructName when it differs from viewStructName
+ // This handles cases like GetSSOClient where response is {"inventories": [...]}
+ // and the response wrapper (GetSSOClientView) must be used instead of the element type (SSOClientInventoryView)
+ String actualViewStruct = (viewStructName != responseStructName) ? responseStructName : viewStructName
if (!useSpec) {
// Check if there are any placeholders
if (placeholders.size() == 0) {
// No placeholder: no uuid parameter needed
// Use GetWithRespKey with empty responseKey to parse whole response
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
-\tvar resp view.${viewStructName}
-\tif err := cli.GetWithRespKey("${cleanPath}", "", "", nil, &resp); err != nil {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${actualViewStruct}, error) {
+\tvar resp view.${actualViewStruct}
+\tif err := cli.GetWithRespKey(ctx, "${cleanPath}", "", "", nil, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp, nil
@@ -692,9 +742,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""
} else {
// Single placeholder: use GetWithRespKey with uuid
- return """func (cli *ZSClient) ${clzName}(uuid string) (*view.${viewStructName}, error) {
-\tvar resp view.${viewStructName}
-\tif err := cli.GetWithRespKey("${cleanPath}", uuid, "", nil, &resp); err != nil {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string) (*view.${actualViewStruct}, error) {
+\tvar resp view.${actualViewStruct}
+\tif err := cli.GetWithRespKey(ctx, "${cleanPath}", uuid, "", nil, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp, nil
@@ -709,9 +759,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
String spec = buildSpecPath(remainingPlaceholders)
- return """func (cli *ZSClient) ${clzName}(${params}) (*view.${viewStructName}, error) {
-\tvar resp view.${viewStructName}
-\terr := cli.GetWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp)
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}) (*view.${actualViewStruct}, error) {
+\tvar resp view.${actualViewStruct}
+\terr := cli.GetWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "${allTo}", nil, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -756,9 +806,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String paramName = toSafeGoParamName(placeholders[0])
if (isActionApi) {
// Action APIs wrap params.Params inside a map
- return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\tif err := cli.Put("${cleanPath}", ${paramName}, map[string]interface{}{
+\tif err := cli.Put(ctx, "${cleanPath}", ${paramName}, map[string]interface{}{
\t\t"${actionKey}": params.Params,
\t}, &resp); err != nil {
\t\treturn nil, err
@@ -767,9 +817,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
"""
} else {
- return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\tif err := cli.Put("${cleanPath}", ${paramName}, params, &resp); err != nil {
+\tif err := cli.Put(ctx, "${cleanPath}", ${paramName}, params, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp.${fieldName}, nil
@@ -781,9 +831,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
if (!hasParams) {
// No params: don't require user input
if (isActionApi) {
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\tif err := cli.Put("${cleanPath}", "", map[string]interface{}{
+\tif err := cli.Put(ctx, "${cleanPath}", "", map[string]interface{}{
\t\t"${actionKey}": map[string]interface{}{},
\t}, &resp); err != nil {
\t\treturn nil, err
@@ -792,9 +842,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
"""
} else {
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\tif err := cli.Put("${cleanPath}", "", map[string]interface{}{}, &resp); err != nil {
+\tif err := cli.Put(ctx, "${cleanPath}", "", map[string]interface{}{}, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp.${fieldName}, nil
@@ -802,9 +852,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""
}
} else if (isActionApi) {
- return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\tif err := cli.Put("${cleanPath}", "", map[string]interface{}{
+\tif err := cli.Put(ctx, "${cleanPath}", "", map[string]interface{}{
\t\t"${actionKey}": params.Params,
\t}, &resp); err != nil {
\t\treturn nil, err
@@ -813,9 +863,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
"""
} else {
- return """func (cli *ZSClient) ${clzName}(uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\tif err := cli.Put("${cleanPath}", uuid, params, &resp); err != nil {
+\tif err := cli.Put(ctx, "${cleanPath}", uuid, params, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp.${fieldName}, nil
@@ -831,9 +881,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
String spec = buildSpecPath(remainingPlaceholders)
- return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tvar resp view.${responseStructName}
-\terr := cli.PutWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", params, &resp)
+\terr := cli.PutWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", params, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -850,9 +900,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String paramName = toSafeGoParamName(placeholders[0])
if (isActionApi) {
// Action APIs wrap params.Params inside a map
- return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tresp := view.${viewStructName}{}
-\tif err := cli.PutWithRespKey("${cleanPath}", ${paramName}, "", map[string]interface{}{
+\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", ${paramName}, "", map[string]interface{}{
\t\t"${actionKey}": params.Params,
\t}, &resp); err != nil {
\t\treturn nil, err
@@ -861,9 +911,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
"""
} else {
- return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tresp := view.${viewStructName}{}
-\tif err := cli.PutWithRespKey("${cleanPath}", ${paramName}, "", params, &resp); err != nil {
+\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", ${paramName}, "", params, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp, nil
@@ -875,9 +925,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
if (!hasParams) {
// No params: don't require user input
if (isActionApi) {
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) {
\tresp := view.${viewStructName}{}
-\tif err := cli.PutWithRespKey("${cleanPath}", "", "", map[string]interface{}{
+\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", "", "", map[string]interface{}{
\t\t"${actionKey}": map[string]interface{}{},
\t}, &resp); err != nil {
\t\treturn nil, err
@@ -886,9 +936,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
"""
} else {
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) {
\tresp := view.${viewStructName}{}
-\tif err := cli.PutWithRespKey("${cleanPath}", "", "", map[string]interface{}{}, &resp); err != nil {
+\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", "", "", map[string]interface{}{}, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp, nil
@@ -896,9 +946,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""
}
} else if (isActionApi) {
- return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tresp := view.${viewStructName}{}
-\tif err := cli.PutWithRespKey("${cleanPath}", "", "", map[string]interface{}{
+\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", "", "", map[string]interface{}{
\t\t"${actionKey}": params.Params,
\t}, &resp); err != nil {
\t\treturn nil, err
@@ -907,9 +957,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
"""
} else {
- return """func (cli *ZSClient) ${clzName}(uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tresp := view.${viewStructName}{}
-\tif err := cli.PutWithRespKey("${cleanPath}", uuid, "", params, &resp); err != nil {
+\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", uuid, "", params, &resp); err != nil {
\t\treturn nil, err
\t}
\treturn &resp, nil
@@ -925,9 +975,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
String spec = buildSpecPath(remainingPlaceholders)
- return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tresp := view.${viewStructName}{}
-\terr := cli.PutWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", params, &resp)
+\terr := cli.PutWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", params, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -938,6 +988,39 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
}
+ /**
+ * Generate Delete-via-POST method.
+ * Some Delete APIs use POST instead of DELETE (e.g. APIDeleteSSOClientMsg).
+ * These return an Event with {"success": true} and no "inventory" key,
+ * so we must use PostWithRespKey with empty responseKey to avoid "key not found".
+ *
+ * Handles URL placeholders (e.g. /cdp-task/{uuid}/data) by extracting them
+ * as function parameters and building the full path with fmt.Sprintf.
+ */
+ private String generateDeleteViaPostMethod(String apiPath, String responseStructName) {
+ boolean hasParams = hasApiParams()
+ def placeholders = extractUrlPlaceholders(apiPath)
+ String pathExpr = placeholders.isEmpty() ? "\"${removePlaceholders(apiPath)}\"" : buildFullPath(placeholders)
+ String placeholderParams = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
+ String methodParams = "ctx context.Context"
+ if (!placeholderParams.isEmpty()) {
+ methodParams = "${methodParams}, ${placeholderParams}"
+ }
+ if (hasParams) {
+ methodParams = "${methodParams}, params param.${clzName}Param"
+ }
+ String bodyExpr = hasParams ? "params" : "map[string]interface{}{}"
+
+ return """func (cli *ZSClient) ${clzName}(${methodParams}) (*view.${responseStructName}, error) {
+\tresp := view.${responseStructName}{}
+\tif err := cli.PostWithRespKey(ctx, ${pathExpr}, "", ${bodyExpr}, &resp); err != nil {
+\t\treturn nil, err
+\t}
+\treturn &resp, nil
+}
+"""
+ }
+
private String generateDeleteMethod(String apiPath) {
// Extract URL placeholders
def placeholders = extractUrlPlaceholders(apiPath)
@@ -948,8 +1031,8 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
if (!useSpec) {
// Single or no placeholder: use the standard Delete method
- return """func (cli *ZSClient) ${clzName}(uuid string, deleteMode param.DeleteMode) error {
-\treturn cli.Delete("${cleanPath}", uuid, string(deleteMode))
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, deleteMode param.DeleteMode) error {
+\treturn cli.Delete(ctx, "${cleanPath}", uuid, string(deleteMode))
}
"""
} else {
@@ -961,8 +1044,8 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String spec = buildSpecPath(remainingPlaceholders)
String paramsStr = "fmt.Sprintf(\"deleteMode=%s\", deleteMode)"
- return """func (cli *ZSClient) ${clzName}(${params}, deleteMode param.DeleteMode) error {
-\treturn cli.DeleteWithSpec("${cleanPath}", ${firstParam}, ${spec}, ${paramsStr}, nil)
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, deleteMode param.DeleteMode) error {
+\treturn cli.DeleteWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, ${paramsStr}, nil)
}
"""
}
@@ -985,11 +1068,11 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
// Build parameter key, for example expungeImage
String paramKey = clzName.substring(0, 1).toLowerCase() + clzName.substring(1)
- return """func (cli *ZSClient) ${clzName}(uuid string) error {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string) error {
\tparams := map[string]interface{}{
\t\t"${paramKey}": map[string]interface{}{},
\t}
-\treturn cli.Put("${cleanPath}", uuid, params, nil)
+\treturn cli.Put(ctx, "${cleanPath}", uuid, params, nil)
}
"""
}
@@ -1025,9 +1108,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
switch (httpMethod) {
case "GET":
if (!useSpec) {
- return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tvar resp ${respType}
-\tif err := cli.Get("${cleanPath}", "", params, &resp); err != nil {
+\tif err := cli.Get(ctx, "${cleanPath}", "", params, &resp); err != nil {
\t\treturn nil, err
\t}
\t${returnStmt}
@@ -1036,9 +1119,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
} else {
String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
String pathSpec = buildPathSpec(placeholders)
- return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
\tvar resp ${respType}
-\terr := cli.GetWithSpec("${cleanPath}", ${pathSpec}, "", "", params, &resp)
+\terr := cli.GetWithSpec(ctx, "${cleanPath}", ${pathSpec}, "", "${allTo}", params, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -1048,9 +1131,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
case "POST":
if (!useSpec) {
- return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
-\tif err := cli.Post("${cleanPath}", params, &resp); err != nil {
+\tif err := cli.Post(ctx, "${cleanPath}", params, &resp); err != nil {
\t\treturn nil, err
\t}
\t${returnStmt}
@@ -1060,9 +1143,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
// POST lacks *WithSpec helpers; build the full URL manually
String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
String fullPath = buildFullPath(placeholders)
- return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
-\terr := cli.Post(${fullPath}, params, &resp)
+\terr := cli.Post(ctx, ${fullPath}, params, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -1087,7 +1170,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""("${cleanPath}", ${paramName}, "", map[string]interface{}{
\t\t"${actionKey}": map[string]interface{}{},
\t}, &resp)"""
- return """func (cli *ZSClient) ${clzName}(${paramName} string) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string) (*view.${viewStructName}, error) {
\t${respDecl}
\tif err := ${putMethod}${putArgs}; err != nil {
\t\treturn nil, err
@@ -1100,7 +1183,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String putArgs = unwrap ?
"""("${cleanPath}", ${paramName}, map[string]interface{}{}, &resp)""" :
"""("${cleanPath}", ${paramName}, "", map[string]interface{}{}, &resp)"""
- return """func (cli *ZSClient) ${clzName}(${paramName} string) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string) (*view.${viewStructName}, error) {
\t${respDecl}
\tif err := ${putMethod}${putArgs}; err != nil {
\t\treturn nil, err
@@ -1119,7 +1202,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""("${cleanPath}", ${paramName}, "", map[string]interface{}{
\t\t"${actionKey}": params.Params,
\t}, &resp)"""
- return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
\tif err := ${putMethod}${putArgs}; err != nil {
\t\treturn nil, err
@@ -1132,7 +1215,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String putArgs = unwrap ?
"""("${cleanPath}", ${paramName}, params, &resp)""" :
"""("${cleanPath}", ${paramName}, "", params, &resp)"""
- return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
\tif err := ${putMethod}${putArgs}; err != nil {
\t\treturn nil, err
@@ -1154,7 +1237,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""("${cleanPath}", "", "", map[string]interface{}{
\t\t"${actionKey}": map[string]interface{}{},
\t}, &resp)"""
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) {
\t${respDecl}
\tif err := ${putMethod}${putArgs}; err != nil {
\t\treturn nil, err
@@ -1167,7 +1250,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String putArgs = unwrap ?
"""("${cleanPath}", "", map[string]interface{}{}, &resp)""" :
"""("${cleanPath}", "", "", map[string]interface{}{}, &resp)"""
- return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) {
\t${respDecl}
\tif err := ${putMethod}${putArgs}; err != nil {
\t\treturn nil, err
@@ -1185,7 +1268,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
"""("${cleanPath}", "", "", map[string]interface{}{
\t\t"${actionKey}": params.Params,
\t}, &resp)"""
- return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
\tif err := ${putMethod}${putArgs}; err != nil {
\t\treturn nil, err
@@ -1198,7 +1281,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String putArgs = unwrap ?
"""("${cleanPath}", uuid, params, &resp)""" :
"""("${cleanPath}", uuid, "", params, &resp)"""
- return """func (cli *ZSClient) ${clzName}(uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
\tif err := ${putMethod}${putArgs}; err != nil {
\t\treturn nil, err
@@ -1215,9 +1298,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
String spec = buildSpecPath(remainingPlaceholders)
- return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
-\terr := cli.PutWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", params, &resp)
+\terr := cli.PutWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", params, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -1227,8 +1310,8 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
}
case "DELETE":
if (!useSpec) {
- return """func (cli *ZSClient) ${clzName}(uuid string, deleteMode param.DeleteMode) error {
-\treturn cli.Delete("${cleanPath}", uuid, string(deleteMode))
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, deleteMode param.DeleteMode) error {
+\treturn cli.Delete(ctx, "${cleanPath}", uuid, string(deleteMode))
}
"""
} else {
@@ -1239,16 +1322,16 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
String spec = buildSpecPath(remainingPlaceholders)
String paramsStr = "fmt.Sprintf(\"deleteMode=%s\", deleteMode)"
- return """func (cli *ZSClient) ${clzName}(${params}, deleteMode param.DeleteMode) error {
- return cli.DeleteWithSpec("${cleanPath}", ${firstParam}, ${spec}, ${paramsStr}, nil)
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, deleteMode param.DeleteMode) error {
+ return cli.DeleteWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, ${paramsStr}, nil)
}
"""
}
default:
if (!useSpec) {
- return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
-\tif err := cli.Post("${cleanPath}", params, &resp); err != nil {
+\tif err := cli.Post(ctx, "${cleanPath}", params, &resp); err != nil {
\t\treturn nil, err
\t}
\t${returnStmt}
@@ -1258,9 +1341,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
// POST lacks *WithSpec helpers; build the full URL manually
String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ")
String fullPath = buildFullPath(placeholders)
- return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
+ return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) {
\t${respDecl}
-\terr := cli.Post(${fullPath}, params, &resp)
+\terr := cli.Post(ctx, ${fullPath}, params, &resp)
\tif err != nil {
\t\treturn nil, err
\t}
@@ -1291,13 +1374,13 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
def builder = new StringBuilder()
builder.append("\n// ${asyncMethodName} Async\n")
- builder.append("func (cli *ZSClient) ${asyncMethodName}(params param.${clzName}Param) (string, error) {\n")
+ builder.append("func (cli *ZSClient) ${asyncMethodName}(ctx context.Context, params param.${clzName}Param) (string, error) {\n")
builder.append("\n")
builder.append("\tresource := \"${resource}\"\n")
builder.append("\tresponseKey := \"\"\n")
builder.append("\tvar retVal interface{}\n")
builder.append("\n")
- builder.append("\tapiId, err := cli.PostWithAsync(resource, responseKey, params, retVal, true)\n")
+ builder.append("\tapiId, err := cli.PostWithAsync(ctx, resource, responseKey, params, retVal, true)\n")
builder.append("\tif err != nil {\n")
builder.append("\t\treturn \"\", err\n")
builder.append("\t}\n")
@@ -1451,4 +1534,3 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err
return "fmt.Sprintf(\"${formatStr}\", ${params})"
}
}
-
diff --git a/rest/src/main/resources/scripts/GoInventory.groovy b/rest/src/main/resources/scripts/GoInventory.groovy
index c31e44149c6..e14cba4f679 100644
--- a/rest/src/main/resources/scripts/GoInventory.groovy
+++ b/rest/src/main/resources/scripts/GoInventory.groovy
@@ -106,6 +106,27 @@ class GoInventory implements SdkTemplate {
return longJobMappings
}
+ /**
+ * Reset all static and instance state for clean re-generation.
+ * Must be called before each generate() to avoid stale caches
+ * causing skipped or duplicate output across repeated runs.
+ */
+ void reset() {
+ longJobMappings.clear()
+ allApiTemplates.clear()
+ inventories.clear()
+ markedInventories.clear()
+ additionalClasses.clear()
+ generatedViewStructs.clear()
+ generatedViewFiles.clear()
+ paramNestedTypes.clear()
+ generatedParamStructs.clear()
+ generatedClientMethods.clear()
+ generatingForParam = false
+ currentGeneratingClass = null
+ logger.warn("[GoSDK] Reset GoInventory state (static + instance)")
+ }
+
/**
* Pre-analyze all API classes once and cache metadata.
* This avoids expensive re-instantiation of GoApiTemplate and redundant logging.
@@ -124,7 +145,7 @@ class GoInventory implements SdkTemplate {
try {
GoApiTemplate template = new GoApiTemplate(apiClass, this)
// If it's a valid template (has @RestRequest)
- if (template.at != null) {
+ if (template.isValid()) {
allApiTemplates.add(template)
}
} catch (Throwable e) {
@@ -160,6 +181,9 @@ class GoInventory implements SdkTemplate {
List generate() {
def files = []
+ GoApiTemplate.reset()
+ reset()
+
logger.warn("[GoSDK] ===== GoInventory.generate() START =====")
logger.warn("[GoSDK] GoInventory.generate() starting...")
@@ -296,7 +320,7 @@ class GoInventory implements SdkTemplate {
content.append("// Copyright (c) ZStack.io, Inc.\n\n")
content.append("package view\n\n")
content.append("import \"time\"\n\n")
- content.append("var _ = time.Now // avoid unused import\n\n")
+ content.append("var _ = time.Now() // avoid unused import\n\n")
classes.each { Class clz ->
String structName = getViewStructName(clz)
@@ -314,401 +338,6 @@ class GoInventory implements SdkTemplate {
return files
}
- /**
- * Generate ZSClient base file
- * @deprecated client.go is manually maintained, this method should not be used
- */
- @Deprecated
- private SdkFile generateClientFile() {
- def sdkFile = new SdkFile()
- sdkFile.subPath = "/pkg/client/"
- sdkFile.fileName = "client.go"
-
- def content = new StringBuilder()
- content.append("// Copyright (c) ZStack.io, Inc.\n\n")
- content.append("package client\n\n")
- content.append("import (\n")
- content.append("\t\"bytes\"\n")
- content.append("\t\"crypto/sha512\"\n")
- content.append("\t\"encoding/hex\"\n")
- content.append("\t\"encoding/json\"\n")
- content.append("\t\"fmt\"\n")
- content.append("\t\"io\"\n")
- content.append("\t\"net/http\"\n")
- content.append("\t\"net/url\"\n")
- content.append("\t\"strconv\"\n")
- content.append("\t\"strings\"\n")
- content.append("\t\"github.com/zstackio/zstack-sdk-go-v2/pkg/param\"\n")
- content.append("\t\"time\"\n")
- content.append(")\n\n")
- content.append("// AuthType authentication type\n")
- content.append("type AuthType string\n\n")
- content.append("const (\n")
- content.append("\tAuthTypeAccessKey AuthType = \"accesskey\"\n")
- content.append("\tAuthTypeLogin AuthType = \"login\"\n")
- content.append(")\n\n")
- content.append("const (\n")
- content.append("\tdefaultZStackPort = 8080\n")
- content.append(")\n\n")
- content.append("// ZSConfig client configuration\n")
- content.append("type ZSConfig struct {\n")
- content.append("\thostname string\n")
- content.append("\tport int\n")
- content.append("\tcontextPath string\n")
- content.append("\taccessKeyId string\n")
- content.append("\taccessKeySecret string\n")
- content.append("\tusername string\n")
- content.append("\tpassword string\n")
- content.append("\tauthType AuthType\n")
- content.append("\tdebug bool\n")
- content.append("\ttimeout time.Duration\n")
- content.append("}\n\n")
- content.append("// NewZSConfig creates a new configuration\n")
- content.append("func NewZSConfig(hostname string, port int, contextPath string) *ZSConfig {\n")
- content.append("\treturn &ZSConfig{\n")
- content.append("\t\thostname: hostname,\n")
- content.append("\t\tport: port,\n")
- content.append("\t\tcontextPath: contextPath,\n")
- content.append("\t\ttimeout: 30 * time.Second,\n")
- content.append("\t}\n")
- content.append("}\n\n")
- content.append("// DefaultZSConfig creates a default configuration\n")
- content.append("func DefaultZSConfig(hostname, contextPath string) *ZSConfig {\n")
- content.append("\treturn NewZSConfig(hostname, defaultZStackPort, contextPath)\n")
- content.append("}\n\n")
- content.append("// AccessKey sets access key authentication\n")
- content.append("func (config *ZSConfig) AccessKey(id, secret string) *ZSConfig {\n")
- content.append("\tconfig.accessKeyId = id\n")
- content.append("\tconfig.accessKeySecret = secret\n")
- content.append("\tconfig.authType = AuthTypeAccessKey\n")
- content.append("\treturn config\n")
- content.append("}\n\n")
- content.append("// Login sets login authentication\n")
- content.append("func (config *ZSConfig) Login(username, password string) *ZSConfig {\n")
- content.append("\tconfig.username = username\n")
- content.append("\tconfig.password = password\n")
- content.append("\tconfig.authType = AuthTypeLogin\n")
- content.append("\treturn config\n")
- content.append("}\n\n")
- content.append("// Debug enables debug mode\n")
- content.append("func (config *ZSConfig) Debug(debug bool) *ZSConfig {\n")
- content.append("\tconfig.debug = debug\n")
- content.append("\treturn config\n")
- content.append("}\n\n")
- content.append("// ZSClient ZStack API client\n")
- content.append("type ZSClient struct {\n")
- content.append("\tconfig *ZSConfig\n")
- content.append("\thttpClient *http.Client\n")
- content.append("\tsessionId string\n")
- content.append("}\n\n")
- content.append("// JobView job inventory view\n")
- content.append("type JobView struct {\n")
- content.append("\tUUID string `json:\"uuid\"`\n")
- content.append("\tState string `json:\"state\"`\n")
- content.append("\tResult interface{} `json:\"result,omitempty\"`\n")
- content.append("\tError interface{} `json:\"error,omitempty\"`\n")
- content.append("\tCreateDate string `json:\"createDate\"`\n")
- content.append("}\n\n")
- content.append("const (\n")
- content.append("\tJobStateProcessing = \"Processing\"\n")
- content.append("\tJobStateSucceeded = \"Succeeded\"\n")
- content.append("\tJobStateFailed = \"Failed\"\n")
- content.append(")\n\n")
- content.append("// NewZSClient creates a new ZStack client\n")
- content.append("func NewZSClient(config *ZSConfig) *ZSClient {\n")
- content.append("\t// Auto-encrypt password for login authentication\n")
- content.append("\tif config.authType == AuthTypeLogin && config.password != \"\" {\n")
- content.append("\t\tconfig.password = hashPasswordSHA512(config.password)\n")
- content.append("\t\tif config.debug {\n")
- content.append("\t\t\tfmt.Printf(\"[DEBUG] Password hashed: %s...\\n\", config.password[:16])\n")
- content.append("\t\t}\n")
- content.append("\t}\n")
- content.append("\treturn &ZSClient{\n")
- content.append("\t\tconfig: config,\n")
- content.append("\t\thttpClient: &http.Client{\n")
- content.append("\t\t\tTimeout: config.timeout,\n")
- content.append("\t\t},\n")
- content.append("\t}\n")
- content.append("}\n\n")
- content.append("// hashPasswordSHA512 encrypts password using SHA512\n")
- content.append("func hashPasswordSHA512(password string) string {\n")
- content.append("\thash := sha512.Sum512([]byte(password))\n")
- content.append("\treturn hex.EncodeToString(hash[:])\n")
- content.append("}\n\n")
- content.append("func (cli *ZSClient) baseURL() string {\n")
- content.append("\treturn fmt.Sprintf(\"http://%s:%d%s\", cli.config.hostname, cli.config.port, cli.config.contextPath)\n")
- content.append("}\n\n")
- content.append("// Get performs a GET request\n")
- content.append("func (cli *ZSClient) Get(path string, uuid string, params interface{}, result interface{}) error {\n")
- content.append("\turl := fmt.Sprintf(\"%s/%s\", cli.baseURL(), path)\n")
- content.append("\tif uuid != \"\" {\n")
- content.append("\t\turl = fmt.Sprintf(\"%s/%s\", url, uuid)\n")
- content.append("\t}\n")
- content.append("\treturn cli.doRequest(\"GET\", url, nil, result)\n")
- content.append("}\n\n")
- content.append("func (cli *ZSClient) QueryJob(uuid string) (*JobView, error) {\n")
- content.append("\tvar resp JobView\n")
- content.append("\turl := fmt.Sprintf(\"%s/v1/api-jobs/%s\", cli.baseURL(), uuid)\n")
- content.append("\terr := cli.doRequest(\"GET\", url, nil, &resp)\n")
- content.append("\treturn &resp, err\n")
- content.append("}\n\n")
- content.append("// List performs a list query\n")
- content.append("func (cli *ZSClient) List(path string, params interface{}, result interface{}) error {\n")
- content.append("\tbaseURL := cli.baseURL()\n")
- content.append("\trequestURL := fmt.Sprintf(\"%s/%s\", baseURL, path)\n")
- content.append("\n")
- content.append("\tif params != nil {\n")
- content.append("\t\tif queryParam, ok := params.(*param.QueryParam); ok {\n")
- content.append("\t\t\tqueryString := cli.buildQueryString(queryParam)\n")
- content.append("\t\t\tif queryString != \"\" {\n")
- content.append("\t\t\t\trequestURL = fmt.Sprintf(\"%s?%s\", requestURL, queryString)\n")
- content.append("\t\t\t}\n")
- content.append("\t\t}\n")
- content.append("\t}\n")
- content.append("\n")
- content.append("\t// Unmarshal response into wrapper with inventories field\n")
- content.append("\tvar wrapper struct {\n")
- content.append("\t\tInventories interface{} `json:\"inventories\"`\n")
- content.append("\t\tInventory interface{} `json:\"inventory\"`\n")
- content.append("\t}\n")
- content.append("\n")
- content.append("\tif err := cli.doRequest(\"GET\", requestURL, nil, &wrapper); err != nil {\n")
- content.append("\t\treturn err\n")
- content.append("\t}\n")
- content.append("\n")
- content.append("\t// Try inventories first (plural), then inventory (singular)\n")
- content.append("\tvar data interface{}\n")
- content.append("\tif wrapper.Inventories != nil {\n")
- content.append("\t\tdata = wrapper.Inventories\n")
- content.append("\t} else if wrapper.Inventory != nil {\n")
- content.append("\t\tdata = wrapper.Inventory\n")
- content.append("\t}\n")
- content.append("\n")
- content.append("\t// Re-marshal and unmarshal into the actual result type\n")
- content.append("\tif data != nil {\n")
- content.append("\t\tdataBytes, err := json.Marshal(data)\n")
- content.append("\t\tif err != nil {\n")
- content.append("\t\t\treturn fmt.Errorf(\"failed to marshal data: %v\", err)\n")
- content.append("\t\t}\n")
- content.append("\t\tif cli.config.debug {\n")
- content.append("\t\t\tfmt.Printf(\"[DEBUG] Received %d bytes of inventory data\\n\", len(dataBytes))\n")
- content.append("\t\t}\n")
- content.append("\t\terr = json.Unmarshal(dataBytes, result)\n")
- content.append("\t\tif err != nil {\n")
- content.append("\t\t\treturn fmt.Errorf(\"failed to unmarshal data into result: %v\", err)\n")
- content.append("\t\t}\n")
- content.append("\t\treturn nil\n")
- content.append("\t}\n")
- content.append("\tif cli.config.debug {\n")
- content.append("\t\tfmt.Println(\"[DEBUG] Both inventories and inventory are nil, returning empty result\")\n")
- content.append("\t}\n")
- content.append("\treturn nil\n")
- content.append("}\n\n")
- content.append("// Post performs a POST request\n")
- content.append("func (cli *ZSClient) Post(path string, params interface{}, result interface{}) error {\n")
- content.append("\turl := fmt.Sprintf(\"%s/%s\", cli.baseURL(), path)\n")
- content.append("\treturn cli.doRequest(\"POST\", url, params, result)\n")
- content.append("}\n\n")
- content.append("// Put performs a PUT request\n")
- content.append("func (cli *ZSClient) Put(path string, uuid string, params interface{}, result interface{}) error {\n")
- content.append("\turl := fmt.Sprintf(\"%s/%s/%s\", cli.baseURL(), path, uuid)\n")
- content.append("\treturn cli.doRequest(\"PUT\", url, params, result)\n")
- content.append("}\n\n")
- content.append("// Delete performs a DELETE request\n")
- content.append("func (cli *ZSClient) Delete(path string, uuid string, deleteMode string) error {\n")
- content.append("\turl := fmt.Sprintf(\"%s/%s/%s?deleteMode=%s\", cli.baseURL(), path, uuid, deleteMode)\n")
- content.append("\treturn cli.doRequest(\"DELETE\", url, nil, nil)\n")
- content.append("}\n\n")
- content.append("func (cli *ZSClient) doRequest(method, url string, body interface{}, result interface{}) error {\n")
- content.append("\t// Auto-login if using login auth and no session yet\n")
- content.append("\tif cli.config.authType == AuthTypeLogin && cli.sessionId == \"\" && !strings.HasSuffix(url, \"/accounts/login\") {\n")
- content.append("\t\terr := cli.Login(cli.config.username, cli.config.password)\n")
- content.append("\t\tif err != nil {\n")
- content.append("\t\t\treturn fmt.Errorf(\"auto-login failed: %v\", err)\n")
- content.append("\t\t}\n")
- content.append("\t}\n\n")
- content.append("\tvar bodyReader io.Reader\n")
- content.append("\tvar bodyBytes []byte\n")
- content.append("\tif body != nil {\n")
- content.append("\t\tvar err error\n")
- content.append("\t\tbodyBytes, err = json.Marshal(body)\n")
- content.append("\t\tif err != nil {\n")
- content.append("\t\t\treturn err\n")
- content.append("\t\t}\n")
- content.append("\t\tbodyReader = bytes.NewBuffer(bodyBytes)\n")
- content.append("\t}\n\n")
- content.append("\treq, err := http.NewRequest(method, url, bodyReader)\n")
- content.append("\tif err != nil {\n")
- content.append("\t\treturn err\n")
- content.append("\t}\n\n")
- content.append("\treq.Header.Set(\"Content-Type\", \"application/json\")\n")
- content.append("\tcli.addAuthHeaders(req)\n\n")
- content.append("\tif cli.config.debug && bodyBytes != nil {\n")
- content.append("\t\tfmt.Printf(\"[DEBUG] %s %s\\n\", method, url)\n")
- content.append("\t\tfmt.Printf(\"[DEBUG] Body: %s\\n\", string(bodyBytes))\n")
- content.append("\t\tfmt.Printf(\"[DEBUG] Headers: Authorization=%s\\n\", req.Header.Get(\"Authorization\"))\n")
- content.append("\t}\n\n")
- content.append("\tresp, err := cli.httpClient.Do(req)\n")
- content.append("\tif err != nil {\n")
- content.append("\t\treturn err\n")
- content.append("\t}\n")
- content.append("\tdefer resp.Body.Close()\n\n")
- content.append("\tif resp.StatusCode == 202 {\n")
- content.append("\t\tvar location struct {\n")
- content.append("\t\t\tLocation string `json:\"location\"`\n")
- content.append("\t\t\tUuid string `json:\"org.zstack.header.rest.APIEvent/uuid\"`\n")
- content.append("\t\t}\n")
- content.append("\t\tif err := json.NewDecoder(resp.Body).Decode(&location); err != nil {\n")
- content.append("\t\t\treturn fmt.Errorf(\"failed to decode 202 response: %v\", err)\n")
- content.append("\t\t}\n")
- content.append("\t\tjobUUID := location.Uuid\n")
- content.append("\t\tif jobUUID == \"\" {\n")
- content.append("\t\t\tparts := bytes.Split([]byte(location.Location), []byte(\"/\"))\n")
- content.append("\t\t\tif len(parts) > 0 {\n")
- content.append("\t\t\t\tjobUUID = string(parts[len(parts)-1])\n")
- content.append("\t\t\t}\n")
- content.append("\t\t}\n")
- content.append("\n")
- content.append("\t\tif jobUUID == \"\" {\n")
- content.append("\t\t\treturn fmt.Errorf(\"failed to extract job uuid from 202 response\")\n")
- content.append("\t\t}\n")
- content.append("\n")
- content.append("\t\treturn cli.waitForJob(jobUUID, result)\n")
- content.append("\t}\n\n")
- content.append("\tif resp.StatusCode >= 400 {\n")
- content.append("\t\trespBody, _ := io.ReadAll(resp.Body)\n")
- content.append("\t\terrMsg := fmt.Sprintf(\"API error: %s %s returned status code %d\\n\", method, url, resp.StatusCode)\n")
- content.append("\t\terrMsg += fmt.Sprintf(\"Authorization: %s\\n\", req.Header.Get(\"Authorization\"))\n")
- content.append("\t\terrMsg += fmt.Sprintf(\"Response: %s\", string(respBody))\n")
- content.append("\t\treturn fmt.Errorf(errMsg)\n")
- content.append("\t}\n\n")
- content.append("\tif result != nil {\n")
- content.append("\t\treturn json.NewDecoder(resp.Body).Decode(result)\n")
- content.append("\t}\n")
- content.append("\treturn nil\n")
- content.append("}\n\n")
- content.append("func (cli *ZSClient) waitForJob(jobUUID string, result interface{}) error {\n")
- content.append("\tticker := time.NewTicker(500 * time.Millisecond)\n")
- content.append("\tdefer ticker.Stop()\n")
- content.append("\n")
- content.append("\ttimeout := time.After(30 * time.Minute)\n")
- content.append("\n")
- content.append("\tfor {\n")
- content.append("\t\tselect {\n")
- content.append("\t\tcase <-timeout:\n")
- content.append("\t\t\treturn fmt.Errorf(\"job %s timeout\", jobUUID)\n")
- content.append("\t\tcase <-ticker.C:\n")
- content.append("\t\t\tjob, err := cli.QueryJob(jobUUID)\n")
- content.append("\t\t\tif err != nil {\n")
- content.append("\t\t\t\tcontinue\n")
- content.append("\t\t\t}\n")
- content.append("\n")
- content.append("\t\t\tif job.State == JobStateSucceeded {\n")
- content.append("\t\t\t\tif result != nil && job.Result != nil {\n")
- content.append("\t\t\t\t\tdata, err := json.Marshal(job.Result)\n")
- content.append("\t\t\t\t\tif err != nil {\n")
- content.append("\t\t\t\t\t\treturn fmt.Errorf(\"failed to marshal job result: %v\", err)\n")
- content.append("\t\t\t\t\t}\n")
- content.append("\t\t\t\t\treturn json.Unmarshal(data, result)\n")
- content.append("\t\t\t\t}\n")
- content.append("\t\t\t\treturn nil\n")
- content.append("\t\t\t}\n")
- content.append("\n")
- content.append("\t\t\tif job.State == JobStateFailed {\n")
- content.append("\t\t\t\treturn fmt.Errorf(\"job failed: %v\", job.Error)\n")
- content.append("\t\t\t}\n")
- content.append("\t\t}\n")
- content.append("\t}\n")
- content.append("}\n\n")
- content.append("func (cli *ZSClient) buildQueryString(params *param.QueryParam) string {\n")
- content.append("\tif params == nil {\n")
- content.append("\t\treturn \"\"\n")
- content.append("\t}\n")
- content.append("\tu := url.Values{}\n")
- content.append("\n")
- content.append("\tfor _, q := range params.Conditions {\n")
- content.append("\t\tif q.Name != \"\" && q.Op != \"\" {\n")
- content.append("\t\t\tu.Add(\"q\", fmt.Sprintf(\"%s%s%s\", q.Name, q.Op, q.Value))\n")
- content.append("\t\t} else if q.Value != \"\" {\n")
- content.append("\t\t\tu.Add(\"q\", q.Value)\n")
- content.append("\t\t}\n")
- content.append("\t}\n")
- content.append("\n")
- content.append("\tif params.LimitNum != nil {\n")
- content.append("\t\tu.Set(\"limit\", strconv.Itoa(*params.LimitNum))\n")
- content.append("\t}\n")
- content.append("\tif params.StartNum != nil {\n")
- content.append("\t\tu.Set(\"start\", strconv.Itoa(*params.StartNum))\n")
- content.append("\t}\n")
- content.append("\tif params.Count {\n")
- content.append("\t\tu.Set(\"count\", \"true\")\n")
- content.append("\t}\n")
- content.append("\tif params.ReplyWithCount {\n")
- content.append("\t\tu.Set(\"replyWithCount\", \"true\")\n")
- content.append("\t}\n")
- content.append("\tif params.GroupBy != \"\" {\n")
- content.append("\t\tu.Set(\"groupBy\", params.GroupBy)\n")
- content.append("\t}\n")
- content.append("\tif params.SortBy != \"\" {\n")
- content.append("\t\tu.Set(\"sortBy\", params.SortBy)\n")
- content.append("\t}\n")
- content.append("\tif params.SortDirection != \"\" {\n")
- content.append("\t\tu.Set(\"sortDirection\", params.SortDirection)\n")
- content.append("\t}\n")
- content.append("\tfor _, f := range params.Fields {\n")
- content.append("\t\tu.Add(\"fields\", f)\n")
- content.append("\t}\n")
- content.append("\n")
- content.append("\treturn u.Encode()\n")
- content.append("}\n\n")
- content.append("func (cli *ZSClient) addAuthHeaders(req *http.Request) {\n")
- content.append("\tif cli.config.authType == AuthTypeAccessKey {\n")
- content.append("\t\treq.Header.Set(\"X-Access-Key-Id\", cli.config.accessKeyId)\n")
- content.append("\t\treq.Header.Set(\"X-Access-Key-Secret\", cli.config.accessKeySecret)\n")
- content.append("\t} else if cli.sessionId != \"\" {\n")
- content.append("\t\treq.Header.Set(\"Authorization\", \"OAuth \"+cli.sessionId)\n")
- content.append("\t}\n")
- content.append("}\n\n")
- content.append("// Login authenticates with username and password\n")
- content.append("func (cli *ZSClient) Login(username, password string) error {\n")
- content.append("\tif cli.config.authType != AuthTypeLogin {\n")
- content.append("\t\treturn fmt.Errorf(\"client is not configured for login authentication\")\n")
- content.append("\t}\n\n")
- content.append("\tvar loginReq = map[string]map[string]string{\n")
- content.append("\t\t\"logInByAccount\": {\n")
- content.append("\t\t\t\"accountName\": username,\n")
- content.append("\t\t\t\"password\": password, // Already hashed in NewZSClient\n")
- content.append("\t\t},\n")
- content.append("\t}\n\n")
- content.append("\tvar loginResp struct {\n")
- content.append("\t\tInventory struct {\n")
- content.append("\t\t\tUUID string `json:\"uuid\"`\n")
- content.append("\t\t} `json:\"inventory\"`\n")
- content.append("\t}\n\n")
- content.append("\turl := fmt.Sprintf(\"%s/v1/accounts/login\", cli.baseURL())\n")
- content.append("\terr := cli.doRequest(\"PUT\", url, loginReq, &loginResp)\n")
- content.append("\tif err != nil {\n")
- content.append("\t\treturn fmt.Errorf(\"login failed: %v\", err)\n")
- content.append("\t}\n\n")
- content.append("\tcli.sessionId = loginResp.Inventory.UUID\n")
- content.append("\tif cli.config.debug {\n")
- content.append("\t\tfmt.Printf(\"[DEBUG] Login successful, sessionId=%s\\n\", cli.sessionId)\n")
- content.append("\t}\n")
- content.append("\treturn nil\n")
- content.append("}\n\n")
- content.append("func (cli *ZSClient) Logout() error {\n")
- content.append("\tif cli.sessionId == \"\" {\n")
- content.append("\t\treturn nil\n")
- content.append("\t}\n\n")
- content.append("\turl := fmt.Sprintf(\"%s/v1/accounts/sessions/%s\", cli.baseURL(), cli.sessionId)\n")
- content.append("\terr := cli.doRequest(\"DELETE\", url, nil, nil)\n")
- content.append("\tcli.sessionId = \"\"\n")
- content.append("\treturn err\n")
- content.append("}\n")
-
- sdkFile.content = content.toString()
- return sdkFile
- }
/**
* Generate base view file
@@ -1728,6 +1357,7 @@ class GoInventory implements SdkTemplate {
content.append("// Copyright (c) ZStack.io, Inc.\n\n")
content.append("package client\n\n")
content.append("import (\n")
+ content.append("\t\"context\"\n")
if (needsFmt) {
content.append("\t\"fmt\"\n")
}
@@ -1760,7 +1390,7 @@ class GoInventory implements SdkTemplate {
content.append("// Copyright (c) ZStack.io, Inc.\n\n")
content.append("package view\n\n")
content.append("import \"time\"\n\n")
- content.append("var _ = time.Now // avoid unused import\n\n")
+ content.append("var _ = time.Now() // avoid unused import\n\n")
int addedCount = 0
Set processedViews = new HashSet<>()
@@ -1912,7 +1542,7 @@ class GoInventory implements SdkTemplate {
content.append("// Copyright (c) ZStack.io, Inc.\n\n")
content.append("package view\n\n")
content.append("import \"time\"\n\n")
- content.append("var _ = time.Now // avoid unused import\n\n")
+ content.append("var _ = time.Now() // avoid unused import\n\n")
logger.warn("[GoSDK] Generating new view file for: ${structName} (${fileName})")
}
@@ -2048,6 +1678,7 @@ class GoInventory implements SdkTemplate {
content.append("// Copyright (c) ZStack.io, Inc.\n\n")
content.append("package client\n\n")
content.append("import (\n")
+ content.append("\t\"context\"\n")
if (needsFmt) {
content.append("\t\"fmt\"\n")
}
@@ -2086,7 +1717,7 @@ class GoInventory implements SdkTemplate {
content.append("// Copyright (c) ZStack.io, Inc.\n\n")
content.append("package param\n\n")
content.append("import \"time\"\n\n")
- content.append("var _ = time.Now // avoid unused import\n\n")
+ content.append("var _ = time.Now() // avoid unused import\n\n")
boolean hasParams = false
allApiTemplates.each { GoApiTemplate template ->
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/rest/src/main/resources/scripts/templates/base_param_types.go.template b/rest/src/main/resources/scripts/templates/base_param_types.go.template
index 962ccda910b..1c3e8ec9ffa 100644
--- a/rest/src/main/resources/scripts/templates/base_param_types.go.template
+++ b/rest/src/main/resources/scripts/templates/base_param_types.go.template
@@ -4,7 +4,7 @@ package param
import "time"
-var _ = time.Now // avoid unused import
+var _ = time.Now() // avoid unused import
type DeleteMode string
diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java
index 1206558cff1..4719de1c0c9 100644
--- a/sdk/src/main/java/SourceClassMap.java
+++ b/sdk/src/main/java/SourceClassMap.java
@@ -25,8 +25,12 @@ public class SourceClassMap {
put("org.zstack.ai.entity.ModelServiceRefInventory", "org.zstack.sdk.ModelServiceRefInventory");
put("org.zstack.ai.entity.ModelServiceTemplateInventory", "org.zstack.sdk.ModelServiceTemplateInventory");
put("org.zstack.ai.entity.TrainedModelRecordInventory", "org.zstack.sdk.TrainedModelRecordInventory");
+ put("org.zstack.ai.entity.VmModelMountInventory", "org.zstack.sdk.VmModelMountInventory");
+ put("org.zstack.ai.entity.VmModelMountStatus", "org.zstack.sdk.VmModelMountStatus");
put("org.zstack.ai.message.ArchitectureImageMapping", "org.zstack.sdk.ArchitectureImageMapping");
put("org.zstack.ai.message.MaaSUsage", "org.zstack.sdk.MaaSUsage");
+ put("org.zstack.ai.message.MatchEvidence", "org.zstack.sdk.MatchEvidence");
+ put("org.zstack.ai.message.MatchedStep", "org.zstack.sdk.MatchedStep");
put("org.zstack.ai.message.ModelCenterServiceInventory", "org.zstack.sdk.ModelCenterServiceInventory");
put("org.zstack.ai.message.ModelCenterServiceInventory$MetaServerService", "org.zstack.sdk.MetaServerService");
put("org.zstack.ai.message.ModelCenterServiceInventory$ServiceStatus", "org.zstack.sdk.ServiceStatus");
@@ -71,6 +75,7 @@ public class SourceClassMap {
put("org.zstack.baremetal2.chassis.ipmi.BareMetal2IpmiChassisInventory", "org.zstack.sdk.BareMetal2IpmiChassisInventory");
put("org.zstack.baremetal2.configuration.BareMetal2ChassisOfferingInventory", "org.zstack.sdk.BareMetal2ChassisOfferingInventory");
put("org.zstack.baremetal2.dpu.BareMetal2DpuHostInventory", "org.zstack.sdk.BareMetal2DpuHostInventory");
+ put("org.zstack.baremetal2.dpu.yucca.YuccaBareMetal2DpuChassisConfig", "org.zstack.sdk.YuccaBareMetal2DpuChassisConfig");
put("org.zstack.baremetal2.gateway.BareMetal2GatewayInventory", "org.zstack.sdk.BareMetal2GatewayInventory");
put("org.zstack.baremetal2.gateway.BareMetal2GatewayProvisionNicInventory", "org.zstack.sdk.BareMetal2GatewayProvisionNicInventory");
put("org.zstack.baremetal2.instance.BareMetal2InstanceInventory", "org.zstack.sdk.BareMetal2InstanceInventory");
@@ -182,6 +187,7 @@ public class SourceClassMap {
put("org.zstack.guesttools.GuestVmScriptInventory", "org.zstack.sdk.GuestVmScriptInventory");
put("org.zstack.guesttools.InvocationRecord", "org.zstack.sdk.InvocationRecord");
put("org.zstack.guesttools.InvocationRecordDetail", "org.zstack.sdk.InvocationRecordDetail");
+ put("org.zstack.ha.HaNetworkGroupInventory", "org.zstack.sdk.HaNetworkGroupInventory");
put("org.zstack.ha.HaStrategyConditionInventory", "org.zstack.sdk.HaStrategyConditionInventory");
put("org.zstack.header.acl.AccessControlListEntryInventory", "org.zstack.sdk.AccessControlListEntryInventory");
put("org.zstack.header.acl.AccessControlListInventory", "org.zstack.sdk.AccessControlListInventory");
@@ -609,6 +615,11 @@ 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.network.zns.L2GeneveNetworkInventory");
+ put("org.zstack.network.zns.ZnsControllerInventory", "org.zstack.sdk.network.zns.ZnsControllerInventory");
+ put("org.zstack.network.zns.ZnsTenantInventory", "org.zstack.sdk.network.zns.ZnsTenantInventory");
+ put("org.zstack.network.zns.ZnsTenantRouterInventory", "org.zstack.sdk.network.zns.ZnsTenantRouterInventory");
+ put("org.zstack.network.zns.ZnsTransportZoneInventory", "org.zstack.sdk.network.zns.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");
@@ -1119,6 +1130,7 @@ public class SourceClassMap {
put("org.zstack.sdk.GuestVmScriptExecutedRecordInventory", "org.zstack.guesttools.GuestVmScriptExecutedRecordInventory");
put("org.zstack.sdk.GuestVmScriptInventory", "org.zstack.guesttools.GuestVmScriptInventory");
put("org.zstack.sdk.H3cSdnControllerTenantInventory", "org.zstack.sdnController.header.H3cSdnControllerTenantInventory");
+ put("org.zstack.sdk.HaNetworkGroupInventory", "org.zstack.ha.HaNetworkGroupInventory");
put("org.zstack.sdk.HaStrategyConditionInventory", "org.zstack.ha.HaStrategyConditionInventory");
put("org.zstack.sdk.HaiTaiSecretResourcePoolInventory", "org.zstack.crypto.securitymachine.thirdparty.haitai.HaiTaiSecretResourcePoolInventory");
put("org.zstack.sdk.HardwareL2VxlanNetworkInventory", "org.zstack.sdnController.header.HardwareL2VxlanNetworkInventory");
@@ -1238,6 +1250,8 @@ public class SourceClassMap {
put("org.zstack.sdk.LunInventory", "org.zstack.header.storageDevice.LunInventory");
put("org.zstack.sdk.MaaSUsage", "org.zstack.ai.message.MaaSUsage");
put("org.zstack.sdk.ManagementNodeInventory", "org.zstack.header.managementnode.ManagementNodeInventory");
+ put("org.zstack.sdk.MatchEvidence", "org.zstack.ai.message.MatchEvidence");
+ put("org.zstack.sdk.MatchedStep", "org.zstack.ai.message.MatchedStep");
put("org.zstack.sdk.MdevDeviceChooser", "org.zstack.pciDevice.virtual.vfio_mdev.MdevDeviceChooser");
put("org.zstack.sdk.MdevDeviceInventory", "org.zstack.pciDevice.virtual.vfio_mdev.MdevDeviceInventory");
put("org.zstack.sdk.MdevDeviceSpecInventory", "org.zstack.pciDevice.specification.mdev.MdevDeviceSpecInventory");
@@ -1565,6 +1579,8 @@ public class SourceClassMap {
put("org.zstack.sdk.VmInstancePciDeviceSpecRefInventory", "org.zstack.pciDevice.specification.pci.VmInstancePciDeviceSpecRefInventory");
put("org.zstack.sdk.VmMemoryBillingInventory", "org.zstack.billing.generator.vm.memory.VmMemoryBillingInventory");
put("org.zstack.sdk.VmMemorySpendingDetails", "org.zstack.billing.spendingcalculator.vm.VmMemorySpendingDetails");
+ put("org.zstack.sdk.VmModelMountInventory", "org.zstack.ai.entity.VmModelMountInventory");
+ put("org.zstack.sdk.VmModelMountStatus", "org.zstack.ai.entity.VmModelMountStatus");
put("org.zstack.sdk.VmNicBandwidthSpendingDetails", "org.zstack.billing.spendingcalculator.vmnic.VmNicBandwidthSpendingDetails");
put("org.zstack.sdk.VmNicInventory", "org.zstack.header.vm.VmNicInventory");
put("org.zstack.sdk.VmNicSecurityGroupRefInventory", "org.zstack.network.securitygroup.VmNicSecurityGroupRefInventory");
@@ -1626,6 +1642,7 @@ public class SourceClassMap {
put("org.zstack.sdk.XmlHookInventory", "org.zstack.kvm.xmlhook.XmlHookInventory");
put("org.zstack.sdk.XmlHookType", "org.zstack.kvm.xmlhook.XmlHookType");
put("org.zstack.sdk.XskyBlockVolumeInventory", "org.zstack.header.volume.block.XskyBlockVolumeInventory");
+ put("org.zstack.sdk.YuccaBareMetal2DpuChassisConfig", "org.zstack.baremetal2.dpu.yucca.YuccaBareMetal2DpuChassisConfig");
put("org.zstack.sdk.ZBoxBackupInventory", "org.zstack.externalbackup.zbox.ZBoxBackupInventory");
put("org.zstack.sdk.ZBoxBackupStorageBackupInfo", "org.zstack.externalbackup.zbox.ZBoxBackupStorageBackupInfo");
put("org.zstack.sdk.ZBoxInventory", "org.zstack.zbox.ZBoxInventory");
@@ -1688,6 +1705,11 @@ public class SourceClassMap {
put("org.zstack.sdk.license.header.server.LicenseUsageDetailView", "org.zstack.license.header.server.LicenseUsageDetailView");
put("org.zstack.sdk.license.header.server.LicenseUsageView", "org.zstack.license.header.server.LicenseUsageView");
put("org.zstack.sdk.license.header.server.TotalLicenseAuthorizedCapacityView", "org.zstack.license.header.server.TotalLicenseAuthorizedCapacityView");
+ put("org.zstack.sdk.network.zns.L2GeneveNetworkInventory", "org.zstack.network.zns.L2GeneveNetworkInventory");
+ put("org.zstack.sdk.network.zns.ZnsControllerInventory", "org.zstack.network.zns.ZnsControllerInventory");
+ put("org.zstack.sdk.network.zns.ZnsTenantInventory", "org.zstack.network.zns.ZnsTenantInventory");
+ put("org.zstack.sdk.network.zns.ZnsTenantRouterInventory", "org.zstack.network.zns.ZnsTenantRouterInventory");
+ put("org.zstack.sdk.network.zns.ZnsTransportZoneInventory", "org.zstack.network.zns.ZnsTransportZoneInventory");
put("org.zstack.sdk.sns.SNSAliyunSmsEndpointInventory", "org.zstack.sns.SNSAliyunSmsEndpointInventory");
put("org.zstack.sdk.sns.SNSApplicationEndpointInventory", "org.zstack.sns.SNSApplicationEndpointInventory");
put("org.zstack.sdk.sns.SNSApplicationPlatformInventory", "org.zstack.sns.SNSApplicationPlatformInventory");
diff --git a/sdk/src/main/java/org/zstack/sdk/AddModelAction.java b/sdk/src/main/java/org/zstack/sdk/AddModelAction.java
index 6e3d3d27bc2..eb9a4a4cbf7 100644
--- a/sdk/src/main/java/org/zstack/sdk/AddModelAction.java
+++ b/sdk/src/main/java/org/zstack/sdk/AddModelAction.java
@@ -73,6 +73,9 @@ public Result throwExceptionIfError() {
@Param(required = false, validValues = {"Public"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
public java.lang.String shareMode;
+ @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String defaultModelServiceUuid;
+
@Param(required = false)
public java.lang.String resourceUuid;
diff --git a/sdk/src/main/java/org/zstack/sdk/AutoMatchModelServiceByModelAction.java b/sdk/src/main/java/org/zstack/sdk/AutoMatchModelServiceByModelAction.java
new file mode 100644
index 00000000000..51765809a69
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/AutoMatchModelServiceByModelAction.java
@@ -0,0 +1,95 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class AutoMatchModelServiceByModelAction 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.AutoMatchModelServiceByModelResult 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, noTrim = false)
+ public java.lang.String modelUuid;
+
+ @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;
+
+
+ private Result makeResult(ApiResult res) {
+ Result ret = new Result();
+ if (res.error != null) {
+ ret.error = res.error;
+ return ret;
+ }
+
+ org.zstack.sdk.AutoMatchModelServiceByModelResult value = res.getResult(org.zstack.sdk.AutoMatchModelServiceByModelResult.class);
+ ret.value = value == null ? new org.zstack.sdk.AutoMatchModelServiceByModelResult() : 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 = "GET";
+ info.path = "/ai/models/{modelUuid}/auto-match-service";
+ info.needSession = true;
+ info.needPoll = false;
+ info.parameterName = "";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/AutoMatchModelServiceByModelResult.java b/sdk/src/main/java/org/zstack/sdk/AutoMatchModelServiceByModelResult.java
new file mode 100644
index 00000000000..9d53b9056fb
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/AutoMatchModelServiceByModelResult.java
@@ -0,0 +1,31 @@
+package org.zstack.sdk;
+
+import org.zstack.sdk.MatchedStep;
+import org.zstack.sdk.MatchEvidence;
+
+public class AutoMatchModelServiceByModelResult {
+ public java.lang.String recommendedServiceUuid;
+ public void setRecommendedServiceUuid(java.lang.String recommendedServiceUuid) {
+ this.recommendedServiceUuid = recommendedServiceUuid;
+ }
+ public java.lang.String getRecommendedServiceUuid() {
+ return this.recommendedServiceUuid;
+ }
+
+ public MatchedStep matchedByStep;
+ public void setMatchedByStep(MatchedStep matchedByStep) {
+ this.matchedByStep = matchedByStep;
+ }
+ public MatchedStep getMatchedByStep() {
+ return this.matchedByStep;
+ }
+
+ public MatchEvidence evidence;
+ public void setEvidence(MatchEvidence evidence) {
+ this.evidence = evidence;
+ }
+ public MatchEvidence getEvidence() {
+ return this.evidence;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/BareMetal2DpuChassisConfig.java b/sdk/src/main/java/org/zstack/sdk/BareMetal2DpuChassisConfig.java
index a4c6b03fd08..6ae70437623 100644
--- a/sdk/src/main/java/org/zstack/sdk/BareMetal2DpuChassisConfig.java
+++ b/sdk/src/main/java/org/zstack/sdk/BareMetal2DpuChassisConfig.java
@@ -4,6 +4,14 @@
public class BareMetal2DpuChassisConfig {
+ public java.lang.String vendorType;
+ public void setVendorType(java.lang.String vendorType) {
+ this.vendorType = vendorType;
+ }
+ public java.lang.String getVendorType() {
+ return this.vendorType;
+ }
+
public java.lang.String ipmiAddress;
public void setIpmiAddress(java.lang.String ipmiAddress) {
this.ipmiAddress = ipmiAddress;
diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeHaNetworkGroupStateAction.java b/sdk/src/main/java/org/zstack/sdk/ChangeHaNetworkGroupStateAction.java
new file mode 100644
index 00000000000..81cd9a1925c
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/ChangeHaNetworkGroupStateAction.java
@@ -0,0 +1,104 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class ChangeHaNetworkGroupStateAction 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.ChangeHaNetworkGroupStateResult 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, noTrim = false)
+ public java.lang.String uuid;
+
+ @Param(required = true, validValues = {"enable","disable"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String stateEvent;
+
+ @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.ChangeHaNetworkGroupStateResult value = res.getResult(org.zstack.sdk.ChangeHaNetworkGroupStateResult.class);
+ ret.value = value == null ? new org.zstack.sdk.ChangeHaNetworkGroupStateResult() : 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 = "PUT";
+ info.path = "/ha/network-groups/{uuid}/actions";
+ info.needSession = true;
+ info.needPoll = true;
+ info.parameterName = "changeHaNetworkGroupState";
+ return info;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeHaNetworkGroupStateResult.java b/sdk/src/main/java/org/zstack/sdk/ChangeHaNetworkGroupStateResult.java
new file mode 100644
index 00000000000..b579aa5771d
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/ChangeHaNetworkGroupStateResult.java
@@ -0,0 +1,14 @@
+package org.zstack.sdk;
+
+import org.zstack.sdk.HaNetworkGroupInventory;
+
+public class ChangeHaNetworkGroupStateResult {
+ public HaNetworkGroupInventory inventory;
+ public void setInventory(HaNetworkGroupInventory inventory) {
+ this.inventory = inventory;
+ }
+ public HaNetworkGroupInventory getInventory() {
+ return this.inventory;
+ }
+
+}
diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeLoadBalancerListenerAction.java b/sdk/src/main/java/org/zstack/sdk/ChangeLoadBalancerListenerAction.java
index 3c056c8af77..b5314b49922 100644
--- a/sdk/src/main/java/org/zstack/sdk/ChangeLoadBalancerListenerAction.java
+++ b/sdk/src/main/java/org/zstack/sdk/ChangeLoadBalancerListenerAction.java
@@ -49,7 +49,7 @@ public Result throwExceptionIfError() {
@Param(required = false, nonempty = false, nullElements = false, emptyString = true, numberRange = {1L,2147483647L}, noTrim = false)
public java.lang.Integer healthCheckInterval;
- @Param(required = false, validValues = {"tcp","udp","http"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ @Param(required = false, validValues = {"tcp","udp","http","none"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
public java.lang.String healthCheckProtocol;
@Param(required = false, validValues = {"GET","HEAD"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
diff --git a/sdk/src/main/java/org/zstack/sdk/CreateHaNetworkGroupAction.java b/sdk/src/main/java/org/zstack/sdk/CreateHaNetworkGroupAction.java
new file mode 100644
index 00000000000..4ec460f54a1
--- /dev/null
+++ b/sdk/src/main/java/org/zstack/sdk/CreateHaNetworkGroupAction.java
@@ -0,0 +1,119 @@
+package org.zstack.sdk;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.zstack.sdk.*;
+
+public class CreateHaNetworkGroupAction 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.CreateHaNetworkGroupResult 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, 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, validValues = {"Flat","Public"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false)
+ public java.lang.String type;
+
+ @Param(required = true, nonempty = false, nullElements = false, emptyString = true, numberRange = {1L,2147483647L}, noTrim = false)
+ public int minAvailableCount = 0;
+
+ @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false)
+ public java.util.List l3NetworkUuids;
+
+ @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.CreateHaNetworkGroupResult value = res.getResult(org.zstack.sdk.CreateHaNetworkGroupResult.class);
+ ret.value = value == null ? new org.zstack.sdk.CreateHaNetworkGroupResult() : value;
+
+ return ret;
+ }
+
+ public Result call() {
+ ApiResult res = ZSClient.call(this);
+ return makeResult(res);
+ }
+
+ public void call(final Completion