desensitized = new ArrayList<>();
+ for (Object url : (List) urls) {
+ desensitized.add(desensitizeUrl(String.valueOf(url)));
+ }
+ config.put(key, desensitized);
+ }
+ }
+
+ private static String desensitizeUrl(String url) {
+ int atIndex = url.lastIndexOf('@');
+ if (atIndex > 0) {
+ int schemeIndex = url.indexOf("://");
+ if (schemeIndex >= 0 && schemeIndex < atIndex) {
+ return url.substring(0, schemeIndex + 3) + "***" + url.substring(atIndex);
+ }
+ return "***" + url.substring(atIndex);
+ }
+ return url;
+ }
+
public String getIdentity() {
return identity;
}
diff --git a/header/src/main/java/org/zstack/header/tag/TagPatternInventory.java b/header/src/main/java/org/zstack/header/tag/TagPatternInventory.java
index e733faea4eb..c9c34e981e5 100644
--- a/header/src/main/java/org/zstack/header/tag/TagPatternInventory.java
+++ b/header/src/main/java/org/zstack/header/tag/TagPatternInventory.java
@@ -27,6 +27,8 @@ public class TagPatternInventory {
private TagPatternType type;
+ private String resourceType;
+
private Timestamp createDate;
private Timestamp lastOpDate;
@@ -39,6 +41,7 @@ public static TagPatternInventory valueOf(TagPatternVO vo) {
inv.value = vo.getValue();
inv.color = vo.getColor();
inv.type = vo.getType();
+ inv.resourceType = vo.getResourceType();
inv.createDate = vo.getCreateDate();
inv.lastOpDate = vo.getLastOpDate();
return inv;
@@ -111,4 +114,12 @@ public TagPatternType getType() {
public void setType(TagPatternType type) {
this.type = type;
}
+
+ public String getResourceType() {
+ return resourceType;
+ }
+
+ public void setResourceType(String resourceType) {
+ this.resourceType = resourceType;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/tag/TagPatternVO.java b/header/src/main/java/org/zstack/header/tag/TagPatternVO.java
index fe35482ffa6..1e7b75647e5 100644
--- a/header/src/main/java/org/zstack/header/tag/TagPatternVO.java
+++ b/header/src/main/java/org/zstack/header/tag/TagPatternVO.java
@@ -30,6 +30,20 @@ public class TagPatternVO extends ResourceVO implements OwnedByAccount {
@Transient
private String accountUuid;
+ /**
+ * Limits this tag pattern to a specific resource type (e.g. "ModelVO").
+ *
+ * NULL means the tag pattern is universal — available for all resource types.
+ * This ensures backward compatibility: tag patterns created before this field
+ * was introduced (upgraded from older versions) have resourceType=NULL and
+ * remain visible everywhere.
+ *
+ * When filtering tag patterns for a specific resource page, use:
+ * {@code WHERE resourceType IS NULL OR resourceType = :targetResourceType}
+ */
+ @Column
+ private String resourceType;
+
@Column
private Timestamp createDate;
@@ -106,4 +120,12 @@ public TagPatternType getType() {
public void setType(TagPatternType type) {
this.type = type;
}
+
+ public String getResourceType() {
+ return resourceType;
+ }
+
+ public void setResourceType(String resourceType) {
+ this.resourceType = resourceType;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/tag/TagPatternVO_.java b/header/src/main/java/org/zstack/header/tag/TagPatternVO_.java
index 7cdc57eb5b4..9e1d541808a 100644
--- a/header/src/main/java/org/zstack/header/tag/TagPatternVO_.java
+++ b/header/src/main/java/org/zstack/header/tag/TagPatternVO_.java
@@ -13,6 +13,7 @@ public class TagPatternVO_ extends ResourceVO_ {
public static volatile SingularAttribute description;
public static volatile SingularAttribute color;
public static volatile SingularAttribute type;
+ public static volatile SingularAttribute resourceType;
public static volatile SingularAttribute createDate;
public static volatile SingularAttribute lastOpDate;
}
diff --git a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
index c00ab47e904..6762f85f793 100644
--- a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsg.java
@@ -2,12 +2,16 @@
import org.springframework.http.HttpMethod;
import org.zstack.header.identity.Action;
+import org.zstack.header.message.APIEvent;
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.APIParam;
import org.zstack.header.network.l3.L3NetworkVO;
+import org.zstack.header.other.APIAuditor;
+import org.zstack.header.other.APIMultiAuditor;
import org.zstack.header.rest.APINoSee;
import org.zstack.header.rest.RestRequest;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -18,7 +22,7 @@
method = HttpMethod.POST,
responseClass = APIChangeVmNicNetworkEvent.class
)
-public class APIChangeVmNicNetworkMsg extends APIMessage implements VmInstanceMessage{
+public class APIChangeVmNicNetworkMsg extends APIMessage implements VmInstanceMessage, APIMultiAuditor {
@APIParam(resourceType = VmNicVO.class, checkAccount = true, operationTarget = true)
private String vmNicUuid;
@@ -33,6 +37,10 @@ public class APIChangeVmNicNetworkMsg extends APIMessage implements VmInstanceMe
private String staticIp;
+
+ @APIParam(required = false)
+ private List dnsAddresses;
+
public String getVmNicUuid() {
return vmNicUuid;
}
@@ -57,6 +65,7 @@ public void setRequiredIpMap(Map> requiredIpMap) {
this.requiredIpMap = requiredIpMap;
}
+
public static APIChangeVmNicNetworkMsg __example__() {
APIChangeVmNicNetworkMsg msg = new APIChangeVmNicNetworkMsg();
msg.vmNicUuid = uuid();
@@ -80,4 +89,20 @@ public String getStaticIp() {
public void setStaticIp(String staticIp) {
this.staticIp = staticIp;
}
+
+ public List getDnsAddresses() {
+ return dnsAddresses;
+ }
+
+ public void setDnsAddresses(List dnsAddresses) {
+ this.dnsAddresses = dnsAddresses;
+ }
+
+ @Override
+ public List multiAudit(APIMessage msg, APIEvent rsp) {
+ APIChangeVmNicNetworkMsg amsg = (APIChangeVmNicNetworkMsg) msg;
+ List res = new ArrayList<>();
+ res.add(new APIAuditor.Result(amsg.getVmInstanceUuid(), VmInstanceVO.class));
+ return res;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsgDoc_zh_cn.groovy
index 893596734c7..c02221bd20e 100644
--- a/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsgDoc_zh_cn.groovy
+++ b/header/src/main/java/org/zstack/header/vm/APIChangeVmNicNetworkMsgDoc_zh_cn.groovy
@@ -66,6 +66,15 @@ doc {
optional true
since "0.6"
}
+ column {
+ name "dnsAddresses"
+ enclosedIn "params"
+ desc "DNS服务器地址列表"
+ location "body"
+ type "List"
+ optional true
+ since "5.5.6"
+ }
}
}
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java b/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
index a4e0d71b209..80a022b5cfb 100755
--- a/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsg.java
@@ -7,6 +7,8 @@
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.rest.RestRequest;
+import java.util.List;
+
/**
* Created by frank on 2/26/2016.
*/
@@ -34,6 +36,8 @@ public class APISetVmStaticIpMsg extends APIMessage implements VmInstanceMessage
private String ipv6Gateway;
@APIParam(required = false)
private String ipv6Prefix;
+ @APIParam(required = false)
+ private List dnsAddresses;
public String getIp() {
return ip;
@@ -100,6 +104,14 @@ public void setIpv6Prefix(String ipv6Prefix) {
this.ipv6Prefix = ipv6Prefix;
}
+ public List getDnsAddresses() {
+ return dnsAddresses;
+ }
+
+ public void setDnsAddresses(List dnsAddresses) {
+ this.dnsAddresses = dnsAddresses;
+ }
+
public static APISetVmStaticIpMsg __example__() {
APISetVmStaticIpMsg msg = new APISetVmStaticIpMsg();
msg.vmInstanceUuid = uuid();
diff --git a/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsgDoc_zh_cn.groovy
index 9415e501788..20513324725 100644
--- a/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsgDoc_zh_cn.groovy
+++ b/header/src/main/java/org/zstack/header/vm/APISetVmStaticIpMsgDoc_zh_cn.groovy
@@ -114,6 +114,15 @@ doc {
optional true
since "0.6"
}
+ column {
+ name "dnsAddresses"
+ enclosedIn "setVmStaticIp"
+ desc "DNS服务器地址列表"
+ location "body"
+ type "List"
+ optional true
+ since "5.5.6"
+ }
}
}
diff --git a/header/src/main/java/org/zstack/header/vm/ChangeVmNicNetworkMsg.java b/header/src/main/java/org/zstack/header/vm/ChangeVmNicNetworkMsg.java
index 1e7f7b1772f..54816bdfcde 100644
--- a/header/src/main/java/org/zstack/header/vm/ChangeVmNicNetworkMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/ChangeVmNicNetworkMsg.java
@@ -14,6 +14,7 @@ public class ChangeVmNicNetworkMsg extends NeedReplyMessage implements VmInstanc
private String vmInstanceUuid;
private Map> requiredIpMap;
private String staticIp;
+ private List dnsAddresses;
public String getVmNicUuid() {
return vmNicUuid;
@@ -55,4 +56,13 @@ public String getStaticIp() {
public void setStaticIp(String staticIp) {
this.staticIp = staticIp;
}
+
+
+ public List getDnsAddresses() {
+ return dnsAddresses;
+ }
+
+ public void setDnsAddresses(List dnsAddresses) {
+ this.dnsAddresses = dnsAddresses;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/vm/SetVmStaticIpMsg.java b/header/src/main/java/org/zstack/header/vm/SetVmStaticIpMsg.java
index dd27c3c0c48..a1a117da8a9 100644
--- a/header/src/main/java/org/zstack/header/vm/SetVmStaticIpMsg.java
+++ b/header/src/main/java/org/zstack/header/vm/SetVmStaticIpMsg.java
@@ -2,6 +2,8 @@
import org.zstack.header.message.NeedReplyMessage;
+import java.util.List;
+
/**
* Created by LiangHanYu on 2022/6/22 17:12
*/
@@ -14,6 +16,7 @@ public class SetVmStaticIpMsg extends NeedReplyMessage implements VmInstanceMess
private String gateway;
private String ipv6Gateway;
private String ipv6Prefix;
+ private List dnsAddresses;
@Override
public String getVmInstanceUuid() {
@@ -79,4 +82,12 @@ public String getIpv6Prefix() {
public void setIpv6Prefix(String ipv6Prefix) {
this.ipv6Prefix = ipv6Prefix;
}
+
+ public List getDnsAddresses() {
+ return dnsAddresses;
+ }
+
+ public void setDnsAddresses(List dnsAddresses) {
+ this.dnsAddresses = dnsAddresses;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/vm/VmAbnormalLifeCycleStruct.java b/header/src/main/java/org/zstack/header/vm/VmAbnormalLifeCycleStruct.java
index 43023821394..7da68329c09 100755
--- a/header/src/main/java/org/zstack/header/vm/VmAbnormalLifeCycleStruct.java
+++ b/header/src/main/java/org/zstack/header/vm/VmAbnormalLifeCycleStruct.java
@@ -72,7 +72,7 @@ boolean match(VmAbnormalLifeCycleStruct struct) {
boolean match(VmAbnormalLifeCycleStruct struct) {
return struct.getOriginalState() == VmInstanceState.Paused
&& struct.getCurrentState() == VmInstanceState.Stopped
- && struct.getCurrentHostUuid().equals(struct.getOriginalHostUuid());
+ && Objects.equals(struct.getCurrentHostUuid(), struct.getOriginalHostUuid());
}
},
VmMigrateToAnotherHost {
diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java
index 896e60e414e..fec2e4f1b51 100755
--- a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java
+++ b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java
@@ -13,6 +13,7 @@ public interface VmInstanceConstant {
// System limit
int MAXIMUM_CDROM_NUMBER = 3;
+ int MAXIMUM_NIC_DNS_NUMBER = 3;
String KVM_HYPERVISOR_TYPE = "KVM";
diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java b/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
index 7007c592aea..99ee2173b98 100755
--- a/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
+++ b/header/src/main/java/org/zstack/header/vm/VmInstanceSpec.java
@@ -847,7 +847,9 @@ public void setBootMode(String bootMode) {
public long getRootDiskAllocateSize() {
if (rootDiskOffering == null) {
- return this.getImageSpec().getInventory().getSize();
+ long virtualSize = this.getImageSpec().getInventory().getSize();
+ long actualSize = this.getImageSpec().getInventory().getActualSize();
+ return Math.max(virtualSize, actualSize);
}
return rootDiskOffering.getDiskSize();
}
diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceState.java b/header/src/main/java/org/zstack/header/vm/VmInstanceState.java
index 8a755b52fda..2f61f0c7e2e 100755
--- a/header/src/main/java/org/zstack/header/vm/VmInstanceState.java
+++ b/header/src/main/java/org/zstack/header/vm/VmInstanceState.java
@@ -168,8 +168,16 @@ public enum VmInstanceState {
new Transaction(VmInstanceStateEvent.destroyed, VmInstanceState.Destroyed),
new Transaction(VmInstanceStateEvent.destroying, VmInstanceState.Destroying),
new Transaction(VmInstanceStateEvent.running, VmInstanceState.Running),
+ new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped),
new Transaction(VmInstanceStateEvent.expunging, VmInstanceState.Expunging)
);
+ // ZSTAC-80898: Expunging safety net — if expunge fails, allow recovery
+ // instead of leaving VM permanently stuck.
+ Expunging.transactions(
+ new Transaction(VmInstanceStateEvent.destroyed, VmInstanceState.Destroyed),
+ new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped),
+ new Transaction(VmInstanceStateEvent.unknown, VmInstanceState.Unknown)
+ );
Destroyed.transactions(
new Transaction(VmInstanceStateEvent.stopped, VmInstanceState.Stopped)
);
diff --git a/header/src/main/java/org/zstack/header/volume/VolumeProtocol.java b/header/src/main/java/org/zstack/header/volume/VolumeProtocol.java
index 8cfebdf6080..aadc4cf15d3 100644
--- a/header/src/main/java/org/zstack/header/volume/VolumeProtocol.java
+++ b/header/src/main/java/org/zstack/header/volume/VolumeProtocol.java
@@ -5,5 +5,6 @@ public enum VolumeProtocol {
iSCSI,
Vhost,
CBD,
- NBD
+ NBD,
+ RBD
}
diff --git a/header/src/main/java/org/zstack/header/volume/VolumeProtocolCapability.java b/header/src/main/java/org/zstack/header/volume/VolumeProtocolCapability.java
index c0ce747a9bb..344badbe8a2 100644
--- a/header/src/main/java/org/zstack/header/volume/VolumeProtocolCapability.java
+++ b/header/src/main/java/org/zstack/header/volume/VolumeProtocolCapability.java
@@ -65,4 +65,12 @@ public boolean isSupportReadonly() {
public void setSupportReadonly(boolean supportReadonly) {
this.supportReadonly = supportReadonly;
}
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public String getHypervisor() {
+ return hypervisor;
+ }
}
diff --git a/header/src/main/java/org/zstack/header/zql/BeforeCallZWatchExtensionPoint.java b/header/src/main/java/org/zstack/header/zql/BeforeCallZWatchExtensionPoint.java
new file mode 100644
index 00000000000..d0ec90621d6
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/zql/BeforeCallZWatchExtensionPoint.java
@@ -0,0 +1,23 @@
+package org.zstack.header.zql;
+
+import java.util.List;
+
+/**
+ * BeforeCallZWatchExtensionPoint is an extension point that allows plugins
+ * to perform custom operations before calling zwatch.
+ */
+public interface BeforeCallZWatchExtensionPoint {
+ /**
+ * Check if this extension supports the given VO class
+ * @param voClass the VO class to check
+ * @return true if this extension supports the VO class, false otherwise
+ */
+ boolean supports(Class> voClass);
+
+ /**
+ * Perform custom operations before calling ZWatch, for example: health-check
+ * @param voClass the VO class type
+ * @param uuids the list of resource UUIDs to process
+ */
+ void beforeCallZWatch(Class> voClass, List uuids);
+}
diff --git a/header/src/main/java/org/zstack/header/zwatch/ResourceMetricBindingExtensionPoint.java b/header/src/main/java/org/zstack/header/zwatch/ResourceMetricBindingExtensionPoint.java
new file mode 100644
index 00000000000..1ec04b18fb4
--- /dev/null
+++ b/header/src/main/java/org/zstack/header/zwatch/ResourceMetricBindingExtensionPoint.java
@@ -0,0 +1,88 @@
+package org.zstack.header.zwatch;
+
+import java.util.List;
+
+public interface ResourceMetricBindingExtensionPoint {
+ class ResourceMetricBinding {
+ private Class> resourceType;
+ private String logicalMetricName;
+ private String sourceNamespace;
+ private String sourceMetricName;
+ private String resourceField;
+ private String sourceLabel;
+ private boolean requireUniqueSourceKey;
+
+ private static T requireValue(String fieldName, T value) {
+ if (value == null) {
+ throw new IllegalStateException(String.format("ResourceMetricBinding.%s must not be null", fieldName));
+ }
+ return value;
+ }
+
+ private static String requireText(String fieldName, String value) {
+ requireValue(fieldName, value);
+ if (value.trim().isEmpty()) {
+ throw new IllegalStateException(String.format("ResourceMetricBinding.%s must not be empty", fieldName));
+ }
+ return value;
+ }
+
+ public Class> getResourceType() {
+ return requireValue("resourceType", resourceType);
+ }
+
+ public void setResourceType(Class> resourceType) {
+ this.resourceType = resourceType;
+ }
+
+ public String getLogicalMetricName() {
+ return requireText("logicalMetricName", logicalMetricName);
+ }
+
+ public void setLogicalMetricName(String logicalMetricName) {
+ this.logicalMetricName = logicalMetricName;
+ }
+
+ public String getSourceNamespace() {
+ return requireText("sourceNamespace", sourceNamespace);
+ }
+
+ public void setSourceNamespace(String sourceNamespace) {
+ this.sourceNamespace = sourceNamespace;
+ }
+
+ public String getSourceMetricName() {
+ return requireText("sourceMetricName", sourceMetricName);
+ }
+
+ public void setSourceMetricName(String sourceMetricName) {
+ this.sourceMetricName = sourceMetricName;
+ }
+
+ public String getResourceField() {
+ return requireText("resourceField", resourceField);
+ }
+
+ public void setResourceField(String resourceField) {
+ this.resourceField = resourceField;
+ }
+
+ public String getSourceLabel() {
+ return requireText("sourceLabel", sourceLabel);
+ }
+
+ public void setSourceLabel(String sourceLabel) {
+ this.sourceLabel = sourceLabel;
+ }
+
+ public boolean isRequireUniqueSourceKey() {
+ return requireUniqueSourceKey;
+ }
+
+ public void setRequireUniqueSourceKey(boolean requireUniqueSourceKey) {
+ this.requireUniqueSourceKey = requireUniqueSourceKey;
+ }
+ }
+
+ List getResourceMetricBindings();
+}
diff --git a/identity/src/main/java/org/zstack/identity/AuthorizationManager.java b/identity/src/main/java/org/zstack/identity/AuthorizationManager.java
index fa50e2b9e8d..daa5152d1f7 100755
--- a/identity/src/main/java/org/zstack/identity/AuthorizationManager.java
+++ b/identity/src/main/java/org/zstack/identity/AuthorizationManager.java
@@ -116,6 +116,11 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti
session = evaluateSession(msg);
}
+ if (session == null) {
+ throw new ApiMessageInterceptionException(err(ORG_ZSTACK_IDENTITY_10012, IdentityErrors.INVALID_SESSION,
+ "evaluated session is null for message[%s]", msg.getMessageName()));
+ }
+
logger.trace(String.format("authorizing message[%s] with user[accountUuid:%s, uuid:%s] session",
msg.getMessageName(),
session.getAccountUuid(),
diff --git a/identity/src/main/java/org/zstack/identity/QuotaUtil.java b/identity/src/main/java/org/zstack/identity/QuotaUtil.java
index d7b29015624..34fce1b5f80 100644
--- a/identity/src/main/java/org/zstack/identity/QuotaUtil.java
+++ b/identity/src/main/java/org/zstack/identity/QuotaUtil.java
@@ -72,7 +72,7 @@ public String getResourceOwnerAccountUuid(String resourceUuid) {
}
@Transactional(readOnly = true)
- public void CheckQuota(QuotaCompareInfo quotaCompareInfo) {
+ public ErrorCode checkQuotaAndReturn(QuotaCompareInfo quotaCompareInfo) {
logger.trace(String.format("dump quota QuotaCompareInfo: \n %s",
JSONObjectUtil.toJsonString(quotaCompareInfo)));
String accountName = Q.New(AccountVO.class)
@@ -80,14 +80,23 @@ public void CheckQuota(QuotaCompareInfo quotaCompareInfo) {
.eq(AccountVO_.uuid, quotaCompareInfo.resourceTargetOwnerAccountUuid)
.findValue();
if (quotaCompareInfo.currentUsed + quotaCompareInfo.request > quotaCompareInfo.quotaValue) {
- throw new ApiMessageInterceptionException(err(ORG_ZSTACK_IDENTITY_10002, IdentityErrors.QUOTA_EXCEEDING,
+ return err(ORG_ZSTACK_IDENTITY_10002, IdentityErrors.QUOTA_EXCEEDING,
"quota exceeding." +
"The resource owner(or target resource owner) account[uuid: %s name: %s] exceeds a quota[name: %s, value: %s], " +
"Current used:%s, Request:%s. Please contact the administrator.",
quotaCompareInfo.resourceTargetOwnerAccountUuid, StringUtils.trimToEmpty(accountName),
quotaCompareInfo.quotaName, quotaCompareInfo.quotaValue,
quotaCompareInfo.currentUsed, quotaCompareInfo.request
- ));
+ );
+ }
+
+ return null;
+ }
+
+ public void CheckQuota(QuotaCompareInfo quotaCompareInfo) {
+ ErrorCode error = checkQuotaAndReturn(quotaCompareInfo);
+ if (error != null) {
+ throw new ApiMessageInterceptionException(error);
}
}
diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java b/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
index 6f0b796e5b8..40786eb7b2e 100755
--- a/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
+++ b/network/src/main/java/org/zstack/network/l2/L2NetworkApiInterceptor.java
@@ -123,6 +123,12 @@ private void validate(APICreateL2NetworkMsg msg) {
} catch (Exception e) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10012, "unsupported vSwitch type[%s]", msg.getvSwitchType()));
}
+
+ if (L2NetworkConstant.VSWITCH_TYPE_LINUX_BRIDGE.equals(msg.getvSwitchType())
+ && (msg.getPhysicalInterface() == null || msg.getPhysicalInterface().trim().isEmpty())) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L2_10021,
+ "physicalInterface is required when vSwitchType is [%s]", msg.getvSwitchType()));
+ }
}
private void validate(APIChangeL2NetworkVlanIdMsg msg) {
diff --git a/network/src/main/java/org/zstack/network/l3/IpRangeHelper.java b/network/src/main/java/org/zstack/network/l3/IpRangeHelper.java
index fffbff32665..64cd7854716 100644
--- a/network/src/main/java/org/zstack/network/l3/IpRangeHelper.java
+++ b/network/src/main/java/org/zstack/network/l3/IpRangeHelper.java
@@ -264,6 +264,95 @@ public static boolean isIpAddressAllocationEnableOnL3(String l3Uuid) {
return l3NetworkVO.enableIpAddressAllocation();
}
+ /**
+ * Check if an IP address is within any L3 network's CIDR (from NormalIpRange).
+ */
+ public static boolean isIpInL3NetworkCidr(String ip, String l3Uuid) {
+ if (ip == null || l3Uuid == null) {
+ return false;
+ }
+
+ if (IPv6NetworkUtils.isIpv6Address(ip)) {
+ List ranges = Q.New(NormalIpRangeVO.class)
+ .eq(NormalIpRangeVO_.l3NetworkUuid, l3Uuid)
+ .eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv6).list();
+ for (NormalIpRangeVO ipr : ranges) {
+ String cidr = ipr.getNetworkCidr();
+ if (cidr != null && IPv6NetworkUtils.isIpv6InCidrRange(ip, cidr)) {
+ return true;
+ }
+ }
+ } else if (NetworkUtils.isIpv4Address(ip)) {
+ List ranges = Q.New(NormalIpRangeVO.class)
+ .eq(NormalIpRangeVO_.l3NetworkUuid, l3Uuid)
+ .eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv4).list();
+ for (NormalIpRangeVO ipr : ranges) {
+ String cidr = ipr.getNetworkCidr();
+ if (cidr != null && NetworkUtils.isIpv4InCidr(ip, cidr)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if an IP address is outside all L3 network CIDRs.
+ */
+ public static boolean isIpOutsideL3NetworkCidr(String ip, String l3Uuid) {
+ return !isIpInL3NetworkCidr(ip, l3Uuid);
+ }
+
+ /**
+ * Find a NormalIpRangeVO whose CIDR contains the given IP.
+ * First tries exact range match (startIp-endIp), then falls back to CIDR match.
+ */
+ public static NormalIpRangeVO findIpRangeByCidr(String ip, List ranges) {
+ if (ip == null || ranges == null || ranges.isEmpty()) {
+ return null;
+ }
+
+ boolean isIpv4 = NetworkUtils.isIpv4Address(ip);
+ boolean isIpv6 = IPv6NetworkUtils.isIpv6Address(ip);
+ int targetVersion = isIpv4 ? IPv6Constants.IPv4 : (isIpv6 ? IPv6Constants.IPv6 : -1);
+ if (targetVersion == -1) {
+ return null;
+ }
+
+ // First try exact range match
+ for (NormalIpRangeVO ipr : ranges) {
+ if (ipr.getIpVersion() != targetVersion) {
+ continue;
+ }
+ if (isIpv4 && NetworkUtils.isInRange(ip, ipr.getStartIp(), ipr.getEndIp())) {
+ return ipr;
+ }
+ if (isIpv6 && IPv6NetworkUtils.isIpv6InRange(ip, ipr.getStartIp(), ipr.getEndIp())) {
+ return ipr;
+ }
+ }
+
+ // Fallback to CIDR match
+ for (NormalIpRangeVO ipr : ranges) {
+ if (ipr.getIpVersion() != targetVersion) {
+ continue;
+ }
+ String cidr = ipr.getNetworkCidr();
+ if (cidr == null) {
+ continue;
+ }
+ if (isIpv4 && NetworkUtils.isIpv4InCidr(ip, cidr)) {
+ return ipr;
+ }
+ if (isIpv6 && IPv6NetworkUtils.isIpv6InCidrRange(ip, cidr)) {
+ return ipr;
+ }
+ }
+
+ return null;
+ }
+
public static IpRangeVO fromIpRangeInventory(IpRangeInventory ipr, String accountUuid) {
NormalIpRangeVO vo = new NormalIpRangeVO();
vo.setUuid(ipr.getUuid() == null ? Platform.getUuid() : ipr.getUuid());
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 5536a5fc487..d7c6c6798d9 100755
--- a/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java
+++ b/network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java
@@ -319,6 +319,7 @@ public void fail(ErrorCode errorCode) {
@Override
public void run(FlowTrigger trigger, Map data) {
+ SQL.New(UsedIpVO.class).eq(UsedIpVO_.ipRangeUuid, iprvo.getUuid()).delete();
dbf.remove(iprvo);
IpRangeHelper.updateL3NetworkIpversion(iprvo);
@@ -555,6 +556,8 @@ public void run(SyncTaskChain chain) {
return;
}
+ ip = overrideUsedIpIfNeeded(msg, ip);
+
logger.debug(String.format("Ip allocator strategy[%s] successfully allocates an ip[%s]", strategyType, ip.getIp()));
reply.setIpInventory(ip);
bus.reply(msg, reply);
@@ -568,6 +571,53 @@ public String getName() {
});
}
+ private UsedIpInventory overrideUsedIpIfNeeded(AllocateIpMsg msg, UsedIpInventory ip) {
+ String overrideNetmask = null;
+ String overrideGateway = null;
+ Integer prefixLength = null;
+
+ if (ip.getIpVersion() != null && ip.getIpVersion() == IPv6Constants.IPv4) {
+ if (msg.getNetmask() != null) {
+ overrideNetmask = msg.getNetmask();
+ }
+ if (msg.getGateway() != null) {
+ overrideGateway = msg.getGateway();
+ }
+ } else if (ip.getIpVersion() != null && ip.getIpVersion() == IPv6Constants.IPv6) {
+ if (msg.getIpv6Prefix() != null) {
+ try {
+ prefixLength = Integer.parseInt(msg.getIpv6Prefix());
+ overrideNetmask = IPv6NetworkUtils.getFormalNetmaskOfNetworkCidr(ip.getIp() + "/" + msg.getIpv6Prefix());
+ } catch (NumberFormatException e) {
+ logger.warn(String.format("failed to parse prefix length[%s], ignore it and use the default prefix length of the ip range",
+ msg.getIpv6Prefix()));
+ }
+ }
+ if (msg.getIpv6Gateway() != null) {
+ overrideGateway = msg.getIpv6Gateway().isEmpty() ? "" : IPv6NetworkUtils.getIpv6AddressCanonicalString(msg.getIpv6Gateway());
+ }
+ }
+
+ if (overrideNetmask != null || overrideGateway != null) {
+ UsedIpVO vo = dbf.findByUuid(ip.getUuid(), UsedIpVO.class);
+ if (vo != null) {
+ if (overrideNetmask != null) {
+ vo.setNetmask(overrideNetmask);
+ }
+ if (overrideGateway != null) {
+ vo.setGateway(overrideGateway);
+ }
+ if (prefixLength != null) {
+ vo.setPrefixLen(prefixLength);
+ }
+ vo = dbf.updateAndRefresh(vo);
+ ip = UsedIpInventory.valueOf(vo);
+ }
+ }
+
+ return ip;
+ }
+
private void handleApiMessage(APIMessage msg) {
if (msg instanceof APIDeleteL3NetworkMsg) {
handle((APIDeleteL3NetworkMsg) msg);
@@ -767,10 +817,7 @@ private void handle(APIDeleteIpAddressMsg msg) {
@Override
public CheckIpAvailabilityReply checkIpAvailability(CheckIpAvailabilityMsg msg) {
CheckIpAvailabilityReply reply = new CheckIpAvailabilityReply();
- int ipversion = IPv6Constants.IPv4;
- if (IPv6NetworkUtils.isIpv6Address(msg.getIp())) {
- ipversion = IPv6Constants.IPv6;
- }
+ final int ipversion = IPv6NetworkUtils.isIpv6Address(msg.getIp()) ? IPv6Constants.IPv6 : IPv6Constants.IPv4;
SimpleQuery rq = dbf.createQuery(IpRangeVO.class);
rq.select(IpRangeVO_.startIp, IpRangeVO_.endIp, IpRangeVO_.gateway);
rq.add(IpRangeVO_.l3NetworkUuid, Op.EQ, self.getUuid());
@@ -1075,6 +1122,13 @@ private void handle(APIGetFreeIpMsg msg) {
}
limit -= freeIpInventorys.size();
}
+
+ Set reservedIpRanges = self.getReservedIpRanges();
+ if (reservedIpRanges != null && !reservedIpRanges.isEmpty()) {
+ freeIpInventorys.removeIf(freeIp -> reservedIpRanges.stream().anyMatch(
+ r -> NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp())));
+ }
+
reply.setInventories(freeIpInventorys);
bus.reply(msg, reply);
diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkApiInterceptor.java b/network/src/main/java/org/zstack/network/l3/L3NetworkApiInterceptor.java
index d4dfa4dc8d6..ac88e09dbf8 100755
--- a/network/src/main/java/org/zstack/network/l3/L3NetworkApiInterceptor.java
+++ b/network/src/main/java/org/zstack/network/l3/L3NetworkApiInterceptor.java
@@ -726,6 +726,48 @@ private void validate(IpRangeInventory ipr) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L3_10064, "new add ip range gateway %s is different from old gateway %s", ipr.getGateway(), r.getGateway()));
}
}
+
+ // When adding the first IpRange, check if network address or gateway is already used
+ if (l3IpRanges.isEmpty()) {
+ String networkAddress = info.getNetworkAddress();
+ String broadcastAddress = info.getBroadcastAddress();
+
+ // Check if gateway address is already used by VmNic with ipRangeUuid=null
+ boolean gatewayUsed = Q.New(UsedIpVO.class)
+ .eq(UsedIpVO_.l3NetworkUuid, ipr.getL3NetworkUuid())
+ .eq(UsedIpVO_.ip, ipr.getGateway())
+ .isNull(UsedIpVO_.ipRangeUuid)
+ .isExists();
+ if (gatewayUsed) {
+ throw new ApiMessageInterceptionException(argerr(
+ ORG_ZSTACK_NETWORK_L3_10079, "gateway address[%s] is already used by a VM NIC, cannot add IP range with this gateway",
+ ipr.getGateway()));
+ }
+
+ // Check if network address is already used
+ boolean networkAddressUsed = Q.New(UsedIpVO.class)
+ .eq(UsedIpVO_.l3NetworkUuid, ipr.getL3NetworkUuid())
+ .eq(UsedIpVO_.ip, networkAddress)
+ .isNull(UsedIpVO_.ipRangeUuid)
+ .isExists();
+ if (networkAddressUsed) {
+ throw new ApiMessageInterceptionException(argerr(
+ ORG_ZSTACK_NETWORK_L3_10080, "network address[%s] is already used by a VM NIC, cannot add IP range containing this address",
+ networkAddress));
+ }
+
+ // Check if broadcast address is already used
+ boolean broadcastAddressUsed = Q.New(UsedIpVO.class)
+ .eq(UsedIpVO_.l3NetworkUuid, ipr.getL3NetworkUuid())
+ .eq(UsedIpVO_.ip, broadcastAddress)
+ .isNull(UsedIpVO_.ipRangeUuid)
+ .isExists();
+ if (broadcastAddressUsed) {
+ throw new ApiMessageInterceptionException(argerr(
+ ORG_ZSTACK_NETWORK_L3_10081, "broadcast address[%s] is already used by a VM NIC, cannot add IP range containing this address",
+ broadcastAddress));
+ }
+ }
} else if (ipr.getIpRangeType() == IpRangeType.AddressPool) {
validateAddressPool(ipr);
}
diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java
index 384a5d2c1df..81fb7e94b3f 100755
--- a/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java
+++ b/network/src/main/java/org/zstack/network/l3/L3NetworkManagerImpl.java
@@ -383,7 +383,7 @@ public IpCapacity call() {
ts = IpRangeHelper.stripNetworkAndBroadcastAddress(ts);
calcElementTotalIp(ts, ret);
- sql = "select count(distinct uip.ip), uip.l3NetworkUuid, uip.ipVersion from UsedIpVO uip where uip.l3NetworkUuid in (:uuids) and (uip.metaData not in (:notAccountMetaData) or uip.metaData IS NULL) group by uip.l3NetworkUuid, uip.ipVersion";
+ sql = "select count(distinct uip.ip), uip.l3NetworkUuid, uip.ipVersion from UsedIpVO uip where uip.l3NetworkUuid in (:uuids) and uip.ipRangeUuid is not null and (uip.metaData not in (:notAccountMetaData) or uip.metaData IS NULL) group by uip.l3NetworkUuid, uip.ipVersion";
TypedQuery cq = dbf.getEntityManager().createQuery(sql, Tuple.class);
cq.setParameter("uuids", msg.getL3NetworkUuids());
cq.setParameter("notAccountMetaData", notAccountMetaDatas);
@@ -399,7 +399,7 @@ public IpCapacity call() {
ts = IpRangeHelper.stripNetworkAndBroadcastAddress(ts);
calcElementTotalIp(ts, ret);
- sql = "select count(distinct uip.ip), zone.uuid, uip.ipVersion from UsedIpVO uip, L3NetworkVO l3, ZoneVO zone where uip.l3NetworkUuid = l3.uuid and l3.zoneUuid = zone.uuid and zone.uuid in (:uuids) and (uip.metaData not in (:notAccountMetaData) or uip.metaData IS NULL) group by zone.uuid, uip.ipVersion";
+ sql = "select count(distinct uip.ip), zone.uuid, uip.ipVersion from UsedIpVO uip, L3NetworkVO l3, ZoneVO zone where uip.l3NetworkUuid = l3.uuid and l3.zoneUuid = zone.uuid and zone.uuid in (:uuids) and uip.ipRangeUuid is not null and (uip.metaData not in (:notAccountMetaData) or uip.metaData IS NULL) group by zone.uuid, uip.ipVersion";
TypedQuery cq = dbf.getEntityManager().createQuery(sql, Tuple.class);
cq.setParameter("uuids", msg.getZoneUuids());
cq.setParameter("notAccountMetaData", notAccountMetaDatas);
@@ -723,6 +723,7 @@ private UsedIpInventory reserveIpv6(IpRangeVO ipRange, String ip, boolean allowD
vo.setL3NetworkUuid(ipRange.getL3NetworkUuid());
vo.setNetmask(ipRange.getNetmask());
vo.setGateway(ipRange.getGateway());
+ vo.setPrefixLen(ipRange.getPrefixLen());
vo.setIpVersion(IPv6Constants.IPv6);
vo = dbf.persistAndRefresh(vo);
return UsedIpInventory.valueOf(vo);
diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkSystemTags.java b/network/src/main/java/org/zstack/network/l3/L3NetworkSystemTags.java
index f6712c54053..c0403be58ed 100644
--- a/network/src/main/java/org/zstack/network/l3/L3NetworkSystemTags.java
+++ b/network/src/main/java/org/zstack/network/l3/L3NetworkSystemTags.java
@@ -1,6 +1,5 @@
package org.zstack.network.l3;
-import org.zstack.header.network.l2.L2NetworkVO;
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.tag.TagDefinition;
import org.zstack.tag.PatternedSystemTag;
diff --git a/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java b/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java
index 6b7d70d45eb..c052d0a1d69 100644
--- a/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java
+++ b/network/src/main/java/org/zstack/network/l3/NormalIpRangeFactory.java
@@ -70,9 +70,11 @@ protected NormalIpRangeVO scripts() {
IpRangeHelper.updateL3NetworkIpversion(vo);
+ // Update UsedIpVO records that have ipRangeUuid=null and IP is within the new range
List usedIpVos = Q.New(UsedIpVO.class)
.eq(UsedIpVO_.l3NetworkUuid, vo.getL3NetworkUuid())
- .eq(UsedIpVO_.ipVersion, vo.getIpVersion()).list();
+ .eq(UsedIpVO_.ipVersion, vo.getIpVersion())
+ .isNull(UsedIpVO_.ipRangeUuid).list();
List updateVos = new ArrayList<>();
for (UsedIpVO ipvo : usedIpVos) {
if (ipvo.getIpVersion() == IPv6Constants.IPv4) {
diff --git a/network/src/main/java/org/zstack/network/service/DhcpExtension.java b/network/src/main/java/org/zstack/network/service/DhcpExtension.java
index 625c872dc8a..8e37da8b1e9 100755
--- a/network/src/main/java/org/zstack/network/service/DhcpExtension.java
+++ b/network/src/main/java/org/zstack/network/service/DhcpExtension.java
@@ -136,11 +136,16 @@ private void populateExtensions() {
}
public boolean isDualStackNicInSingleL3Network(VmNicInventory nic) {
- if (nic.getUsedIps().size() < 2) {
+ // Filter out IPs outside L3 CIDR range
+ List validIps = nic.getUsedIps().stream()
+ .filter(ip -> ip.getIpRangeUuid() != null || IpRangeHelper.isIpInL3NetworkCidr(ip.getIp(), ip.getL3NetworkUuid()))
+ .collect(Collectors.toList());
+
+ if (validIps.size() < 2) {
return false;
}
- return nic.getUsedIps().stream().map(UsedIpInventory::getL3NetworkUuid).distinct().count() == 1;
+ return validIps.stream().map(UsedIpInventory::getL3NetworkUuid).distinct().count() == 1;
}
private DhcpStruct getDhcpStruct(VmInstanceInventory vm, List hostNames, VmNicVO nic, UsedIpVO ip, boolean isDefaultNic) {
@@ -194,7 +199,11 @@ private boolean isEnableRa(String l3Uuid) {
private void setDualStackNicOfSingleL3Network(DhcpStruct struct, VmNicVO nic) {
struct.setIpVersion(IPv6Constants.DUAL_STACK);
- List sortedIps = nic.getUsedIps().stream().sorted(Comparator.comparingLong(UsedIpVO::getIpVersionl)).collect(Collectors.toList());
+ // Filter out IPs outside L3 CIDR range
+ List sortedIps = nic.getUsedIps().stream()
+ .filter(ip -> ip.getIpRangeUuid() != null || IpRangeHelper.isIpInL3NetworkCidr(ip.getIp(), ip.getL3NetworkUuid()))
+ .sorted(Comparator.comparingLong(UsedIpVO::getIpVersionl))
+ .collect(Collectors.toList());
for (UsedIpVO ip : sortedIps) {
if (ip.getIpVersion() == IPv6Constants.IPv4) {
struct.setGateway(ip.getGateway());
@@ -275,6 +284,11 @@ public List makeDhcpStruct(VmInstanceInventory vm, List> workoutDhcp(VmInstanceS
for (VmNicInventory inv : spec.getDestNics()) {
VmNicVO vmNicVO = dbf.findByUuid(inv.getUuid(), VmNicVO.class);
for (UsedIpVO ip : vmNicVO.getUsedIps()) {
+ // Skip IPs outside L3 IP range (not managed by DHCP)
+ if (ip.getIpRangeUuid() == null) {
+ continue;
+ }
+
L3NetworkInventory l3 = l3Map.get(ip.getL3NetworkUuid());
if (l3 == null) {
continue;
diff --git a/network/src/main/java/org/zstack/network/service/NetworkServiceManager.java b/network/src/main/java/org/zstack/network/service/NetworkServiceManager.java
index cd80c9b184f..7d421393afe 100755
--- a/network/src/main/java/org/zstack/network/service/NetworkServiceManager.java
+++ b/network/src/main/java/org/zstack/network/service/NetworkServiceManager.java
@@ -18,6 +18,16 @@ public interface NetworkServiceManager {
void applyNetworkServiceOnChangeIP(VmInstanceSpec spec, NetworkServiceExtensionPoint.NetworkServiceExtensionPosition position, Completion completion);
List getL3NetworkDns(String l3NetworkUuid);
+ /**
+ * Get DNS servers for a VM NIC.
+ * Priority: VM NIC system tag > L3 Network DNS
+ *
+ * @param vmUuid VM instance UUID
+ * @param l3NetworkUuid L3 network UUID
+ * @return List of DNS server addresses
+ */
+ List getVmNicDns(String vmUuid, String l3NetworkUuid);
+
void enableNetworkService(L3NetworkVO l3VO, NetworkServiceProviderType providerType,
NetworkServiceType nsType, List systemTags, Completion completion);
diff --git a/network/src/main/java/org/zstack/network/service/NetworkServiceManagerImpl.java b/network/src/main/java/org/zstack/network/service/NetworkServiceManagerImpl.java
index 5e353fa1ff2..24ab48a51de 100755
--- a/network/src/main/java/org/zstack/network/service/NetworkServiceManagerImpl.java
+++ b/network/src/main/java/org/zstack/network/service/NetworkServiceManagerImpl.java
@@ -25,9 +25,12 @@
import org.zstack.header.network.service.*;
import org.zstack.header.network.service.NetworkServiceExtensionPoint.NetworkServiceExtensionPosition;
import org.zstack.header.vm.*;
+import org.zstack.header.tag.SystemTagVO;
+import org.zstack.header.tag.SystemTagVO_;
import org.zstack.query.QueryFacade;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
+import org.zstack.utils.network.IPv6NetworkUtils;
import java.util.*;
@@ -483,6 +486,36 @@ public List getL3NetworkDns(String l3NetworkUuid){
return dns;
}
+ @Override
+ public List getVmNicDns(String vmUuid, String l3NetworkUuid) {
+ // First try to get DNS from system tag (VM NIC-level custom DNS)
+ // Tag format: staticDns::{l3NetworkUuid}::{dns1,dns2,dns3}
+ String tagLike = String.format("staticDns::%s::%%", l3NetworkUuid);
+ List tags = Q.New(SystemTagVO.class)
+ .select(SystemTagVO_.tag)
+ .eq(SystemTagVO_.resourceUuid, vmUuid)
+ .eq(SystemTagVO_.resourceType, VmInstanceVO.class.getSimpleName())
+ .like(SystemTagVO_.tag, tagLike)
+ .listValues();
+ if (tags != null && !tags.isEmpty()) {
+ String tag = tags.get(0);
+ // Parse DNS part: staticDns::{l3Uuid}::{dnsStr}
+ String prefix = String.format("staticDns::%s::", l3NetworkUuid);
+ if (tag.startsWith(prefix)) {
+ String dnsStr = tag.substring(prefix.length());
+ if (!dnsStr.isEmpty()) {
+ List dnsList = new ArrayList<>();
+ for (String dns : dnsStr.split(",")) {
+ dnsList.add(IPv6NetworkUtils.ipv6TagValueToAddress(dns));
+ }
+ return dnsList;
+ }
+ }
+ }
+ // Fall back to L3 network DNS
+ return getL3NetworkDns(l3NetworkUuid);
+ }
+
@Override
public void instantiateResourceOnAttachingNic(VmInstanceSpec spec, L3NetworkInventory l3, Completion completion) {
preInstantiateVmResource(spec, completion);
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 1d3f453f312..dcada8edeee 100755
--- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
+++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmConstant.java
@@ -31,6 +31,7 @@ public enum BootstrapParams {
sshPort,
uuid,
managementNodeIp,
+ managementNodeVip,
managementNodeCidr,
additionalL3Uuids,
}
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 0faf04c9f65..6d63ce3522f 100755
--- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
+++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java
@@ -462,6 +462,7 @@ public Map prepareBootstrapInformation(VmInstanceSpec spec) {
ret.put(ApplianceVmConstant.BootstrapParams.publicKey.toString(), publicKey);
ret.put(BootstrapParams.uuid.toString(), spec.getVmInventory().getUuid());
ret.put(BootstrapParams.managementNodeIp.toString(), Platform.getManagementServerIp());
+ ret.put(BootstrapParams.managementNodeVip.toString(), Platform.getManagementServerVip());
ret.put(BootstrapParams.managementNodeCidr.toString(), Platform.getManagementServerCidr());
/* this is only used by ApplianceVmPrepareBootstrapInfoExtensionPoint extension point, will be deleted after extension point */
ret.put(BootstrapParams.additionalL3Uuids.toString(), additionalNics.stream().map(VmNicInventory::getL3NetworkUuid).collect(Collectors.toList()));
diff --git a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmNicTO.java b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmNicTO.java
index c9905a78667..2c31637659d 100755
--- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmNicTO.java
+++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmNicTO.java
@@ -35,9 +35,20 @@ public ApplianceVmNicTO(VmNicInventory inv) {
} else {
ip6 = uip.getIp();
gateway6 = uip.getGateway();
- NormalIpRangeVO ipRangeVO = Q.New(NormalIpRangeVO.class).eq(NormalIpRangeVO_.uuid, uip.getIpRangeUuid()).find();
- prefixLength = ipRangeVO.getPrefixLen();
- addressMode = ipRangeVO.getAddressMode();
+ // First try to use prefixLen from UsedIpInventory (for IP outside range)
+ if (uip.getPrefixLen() != null) {
+ prefixLength = uip.getPrefixLen();
+ addressMode = IPv6Constants.SLAAC;
+ }
+ if (uip.getIpRangeUuid() != null) {
+ NormalIpRangeVO ipRangeVO = Q.New(NormalIpRangeVO.class).eq(NormalIpRangeVO_.uuid, uip.getIpRangeUuid()).find();
+ if (ipRangeVO != null) {
+ if (prefixLength == null) {
+ prefixLength = ipRangeVO.getPrefixLen();
+ }
+ addressMode = ipRangeVO.getAddressMode();
+ }
+ }
}
}
/* for virtual router, gateway ip is in the usedIpVO */
diff --git a/plugin/cbd/src/main/java/org/zstack/cbd/kvm/KvmCbdNodeServer.java b/plugin/cbd/src/main/java/org/zstack/cbd/kvm/KvmCbdNodeServer.java
index fe0f69a848a..78439bb113e 100644
--- a/plugin/cbd/src/main/java/org/zstack/cbd/kvm/KvmCbdNodeServer.java
+++ b/plugin/cbd/src/main/java/org/zstack/cbd/kvm/KvmCbdNodeServer.java
@@ -234,12 +234,12 @@ public void run(MessageReply reply) {
private String convertPathIfNeeded(BaseVolumeInfo volumeInfo, HostInventory host){
if (!VolumeProtocol.CBD.name().equals(volumeInfo.getProtocol())){
- return volumeInfo.getInstallPath();
+ return null;
}
PrimaryStorageNodeSvc nodeSvc = getNodeService(volumeInfo);
if (nodeSvc == null) {
- return volumeInfo.getInstallPath();
+ return null;
}
return nodeSvc.getActivePath(volumeInfo, host, false);
@@ -247,7 +247,9 @@ private String convertPathIfNeeded(BaseVolumeInfo volumeInfo, HostInventory host
private void convertAndSetPathIfNeeded(BaseVolumeInfo volumeInfo, HostInventory host, T target, PathSetter setter) {
String newInstallPath = convertPathIfNeeded(volumeInfo, host);
- setter.setPath(target, newInstallPath);
+ if (newInstallPath != null) {
+ setter.setPath(target, newInstallPath);
+ }
}
diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephImageCacheCleaner.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephImageCacheCleaner.java
index 81cec9a040c..032b90d0a6b 100755
--- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephImageCacheCleaner.java
+++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephImageCacheCleaner.java
@@ -34,7 +34,7 @@ protected GlobalConfig cleanupIntervalConfig() {
@Transactional
@Override
protected List createShadowImageCacheVOsForNewDeletedAndOld(String psUuid, ImageCacheCleanParam param) {
- List staleImageCacheIds = getStaleImageCacheIds(psUuid, false);
+ List staleImageCacheIds = getStaleImageCacheIds(psUuid, param.includeReadyImage);
if (staleImageCacheIds == null || staleImageCacheIds.isEmpty()) {
return null;
}
diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
index d80b40a1d6a..c3b01dc3c8b 100755
--- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
+++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
@@ -3974,15 +3974,17 @@ public void done() {
mon.connect(new Completion(releaseLock) {
@Override
public void success() {
+ String monUuid = mon.getSelf() == null ? "unknown" : mon.getSelf().getUuid();
logger.debug(String.format("successfully reconnected the mon[uuid:%s] of the ceph primary" +
- " storage[uuid:%s, name:%s]", mon.getSelf().getUuid(), self.getUuid(), self.getName()));
+ " storage[uuid:%s, name:%s]", monUuid, self.getUuid(), self.getName()));
releaseLock.done();
}
@Override
public void fail(ErrorCode errorCode) {
+ String monUuid = mon.getSelf() == null ? "unknown" : mon.getSelf().getUuid();
logger.warn(String.format("failed to reconnect the mon[uuid:%s] server of the ceph primary" +
- " storage[uuid:%s, name:%s], %s", mon.getSelf().getUuid(), self.getUuid(), self.getName(), errorCode));
+ " storage[uuid:%s, name:%s], %s", monUuid, self.getUuid(), self.getName(), errorCode));
releaseLock.done();
}
});
diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java
index 90b6220d53a..7ca3e710291 100755
--- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java
+++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageMonBase.java
@@ -141,7 +141,7 @@ public void fail(ErrorCode errorCode) {
@Override
public String getName() {
- return String.format("connect-ceph-primary-storage-mon-%s", self.getUuid());
+ return String.format("connect-ceph-primary-storage-mon-%s", self == null ? "unknown" : self.getUuid());
}
});
}
@@ -420,7 +420,7 @@ public void fail(ErrorCode errorCode) {
@Override
public String getName() {
- return String.format("ping-ceph-primary-storage-%s", self.getUuid());
+ return String.format("ping-ceph-primary-storage-%s", self == null ? "unknown" : self.getUuid());
}
});
}
diff --git a/plugin/eip/src/main/java/org/zstack/network/service/eip/EipApiInterceptor.java b/plugin/eip/src/main/java/org/zstack/network/service/eip/EipApiInterceptor.java
index 85e9a357a06..c1ce073cdd8 100755
--- a/plugin/eip/src/main/java/org/zstack/network/service/eip/EipApiInterceptor.java
+++ b/plugin/eip/src/main/java/org/zstack/network/service/eip/EipApiInterceptor.java
@@ -23,6 +23,7 @@
import org.zstack.header.vm.VmNicHelper;
import org.zstack.header.vm.VmNicVO;
import org.zstack.header.vm.VmNicVO_;
+import org.zstack.network.l3.IpRangeHelper;
import org.zstack.network.service.vip.VipNetworkServicesRefVO;
import org.zstack.network.service.vip.VipNetworkServicesRefVO_;
import org.zstack.network.service.vip.VipState;
@@ -202,6 +203,14 @@ public VipVO call() {
} else {
msg.setUsedIpUuid(nic.getUsedIpUuid());
}
+
+ // Check if the IP is outside L3 CIDR range
+ UsedIpVO usedIpVO = dbf.findByUuid(msg.getUsedIpUuid(), UsedIpVO.class);
+ if (usedIpVO != null && IpRangeHelper.isIpOutsideL3NetworkCidr(usedIpVO.getIp(), usedIpVO.getL3NetworkUuid())) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_EIP_10024,
+ "cannot bind EIP to IP address[%s] which is outside L3 network CIDR range",
+ usedIpVO.getIp()));
+ }
}
private void validate(APIDetachEipMsg msg) {
@@ -304,6 +313,14 @@ private void validate(APICreateEipMsg msg) {
if (msg.getUsedIpUuid() != null) {
isVipInVmNicSubnet(msg.getVipUuid(), msg.getUsedIpUuid());
+
+ // Check if the IP is outside L3 CIDR range
+ UsedIpVO usedIpVO = dbf.findByUuid(msg.getUsedIpUuid(), UsedIpVO.class);
+ if (usedIpVO != null && IpRangeHelper.isIpOutsideL3NetworkCidr(usedIpVO.getIp(), usedIpVO.getL3NetworkUuid())) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_EIP_10024,
+ "cannot bind EIP to IP address[%s] which is outside L3 network CIDR range",
+ usedIpVO.getIp()));
+ }
}
checkNicRule(msg.getVmNicUuid());
diff --git a/plugin/eip/src/main/java/org/zstack/network/service/eip/EipManagerImpl.java b/plugin/eip/src/main/java/org/zstack/network/service/eip/EipManagerImpl.java
index 8e23e2a9175..dae4a3e10bf 100755
--- a/plugin/eip/src/main/java/org/zstack/network/service/eip/EipManagerImpl.java
+++ b/plugin/eip/src/main/java/org/zstack/network/service/eip/EipManagerImpl.java
@@ -34,6 +34,7 @@
import org.zstack.header.query.ExpandedQueryStruct;
import org.zstack.header.vm.*;
import org.zstack.identity.AccountManager;
+import org.zstack.network.l3.IpRangeHelper;
import org.zstack.network.l3.L3NetworkManager;
import org.zstack.network.service.NetworkServiceManager;
import org.zstack.network.service.vip.*;
@@ -416,6 +417,15 @@ private List getAttachableVmNicForEip(VipInventory vip, APIGetEi
} else {
ret = l3Mgr.filterVmNicByIpVersion(VmNicInventory.valueOf(nics), IPv6Constants.IPv4);
}
+
+ // Filter out NICs whose primary IP is outside L3 CIDR
+ final int targetIpVersion = NetworkUtils.isIpv4Address(vip.getIp()) ? IPv6Constants.IPv4 : IPv6Constants.IPv6;
+ ret = ret.stream().filter(nic -> {
+ return nic.getUsedIps().stream()
+ .filter(ip -> ip.getIpVersion() == targetIpVersion)
+ .anyMatch(ip -> IpRangeHelper.isIpInL3NetworkCidr(ip.getIp(), ip.getL3NetworkUuid()));
+ }).collect(Collectors.toList());
+
return ret;
}
diff --git a/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java b/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
index 43b2aad3e3f..df5b1081d08 100644
--- a/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
+++ b/plugin/expon/src/main/java/org/zstack/expon/ExponStorageController.java
@@ -122,6 +122,11 @@ public ExponStorageController(String url) {
ExponConnectConfig clientConfig = new ExponConnectConfig();
clientConfig.hostname = uri.getHost();
clientConfig.port = uri.getPort();
+ String scheme = uri.getScheme();
+ clientConfig.scheme = scheme != null ? scheme : "https";
+ if (clientConfig.port == -1) {
+ clientConfig.port = "https".equalsIgnoreCase(clientConfig.scheme) ? 443 : 80;
+ }
clientConfig.readTimeout = TimeUnit.MINUTES.toMillis(10);
clientConfig.writeTimeout = TimeUnit.MINUTES.toMillis(10);
ExponClient client = new ExponClient();
diff --git a/plugin/expon/src/main/java/org/zstack/expon/ExponStorageFactory.java b/plugin/expon/src/main/java/org/zstack/expon/ExponStorageFactory.java
index 4086058d198..db194c9a415 100644
--- a/plugin/expon/src/main/java/org/zstack/expon/ExponStorageFactory.java
+++ b/plugin/expon/src/main/java/org/zstack/expon/ExponStorageFactory.java
@@ -8,6 +8,7 @@
import org.zstack.header.volume.VolumeInventory;
import org.zstack.header.volume.VolumeProtocol;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -48,7 +49,7 @@ public String getIdentity() {
@Override
public List getPreferBackupStorageTypes() {
- return preferBackupStorageTypes;
+ return new ArrayList<>(preferBackupStorageTypes);
}
public void setPreferBackupStorageTypes(List preferBackupStorageTypes) {
diff --git a/plugin/expon/src/main/java/org/zstack/expon/sdk/ExponClient.java b/plugin/expon/src/main/java/org/zstack/expon/sdk/ExponClient.java
index 53d087cceac..5ee79354252 100644
--- a/plugin/expon/src/main/java/org/zstack/expon/sdk/ExponClient.java
+++ b/plugin/expon/src/main/java/org/zstack/expon/sdk/ExponClient.java
@@ -211,7 +211,7 @@ private ApiResult pollResult(String taskId) {
private void fillQueryApiRequestBuilder(Request.Builder reqBuilder) throws Exception {
ExponQueryRequest qaction = (ExponQueryRequest) action;
- HttpUrl.Builder urlBuilder = new HttpUrl.Builder().scheme("https")
+ HttpUrl.Builder urlBuilder = new HttpUrl.Builder().scheme(config.scheme)
.host(config.hostname)
.port(config.port);
@@ -262,7 +262,7 @@ private void fillQueryApiRequestBuilder(Request.Builder reqBuilder) throws Excep
private void fillNonQueryApiRequestBuilder(Request.Builder reqBuilder) throws Exception {
HttpUrl.Builder builder = new HttpUrl.Builder()
- .scheme("https")
+ .scheme(config.scheme)
.host(config.hostname)
.port(config.port);
builder.addPathSegment("api");
diff --git a/plugin/expon/src/main/java/org/zstack/expon/sdk/ExponConnectConfig.java b/plugin/expon/src/main/java/org/zstack/expon/sdk/ExponConnectConfig.java
index 91725847302..912c247d061 100644
--- a/plugin/expon/src/main/java/org/zstack/expon/sdk/ExponConnectConfig.java
+++ b/plugin/expon/src/main/java/org/zstack/expon/sdk/ExponConnectConfig.java
@@ -5,6 +5,7 @@
public class ExponConnectConfig {
public String hostname = "localhost";
public int port = 443;
+ public String scheme = "https";
long defaultPollingTimeout = TimeUnit.HOURS.toMillis(3);
long defaultPollingInterval = TimeUnit.SECONDS.toMillis(1);
public Long readTimeout;
@@ -39,6 +40,11 @@ public Builder setPort(int port) {
return this;
}
+ public Builder setScheme(String scheme) {
+ config.scheme = scheme;
+ return this;
+ }
+
public Builder setDefaultPollingTimeout(long value, TimeUnit unit) {
config.defaultPollingTimeout = unit.toMillis(value);
return this;
diff --git a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatDhcpBackend.java b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatDhcpBackend.java
index 962be8fe4ef..5823bb2b9ee 100755
--- a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatDhcpBackend.java
+++ b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatDhcpBackend.java
@@ -18,8 +18,7 @@
import org.zstack.core.db.SQL;
import org.zstack.core.defer.Defer;
import org.zstack.core.defer.Deferred;
-import org.zstack.core.thread.SyncTask;
-import org.zstack.core.thread.ThreadFacade;
+import org.zstack.core.thread.*;
import org.zstack.core.upgrade.GrayVersion;
import org.zstack.core.workflow.SimpleFlowChain;
import org.zstack.header.AbstractService;
@@ -121,6 +120,48 @@ public class FlatDhcpBackend extends AbstractService implements NetworkServiceDh
private Map getIpStatisticExts = new HashMap<>();
+ private static class DhcpApplyRequest {
+ final String hostUuid;
+ final List dhcpInfos;
+ final boolean rebuild;
+
+ DhcpApplyRequest(String hostUuid, List dhcpInfos, boolean rebuild) {
+ this.hostUuid = hostUuid;
+ this.dhcpInfos = dhcpInfos;
+ this.rebuild = rebuild;
+ }
+ }
+
+ private class DhcpApplyQueue extends CoalesceQueue {
+ @Override
+ protected String getName() {
+ return "flat-dhcp-apply";
+ }
+
+ @Override
+ protected void executeBatch(List requests, Completion completion) {
+ if (requests.isEmpty()) {
+ completion.success();
+ return;
+ }
+
+ String hostUuid = requests.get(0).hostUuid;
+
+ boolean anyRebuild = false;
+ List mergedInfos = new ArrayList<>();
+ for (DhcpApplyRequest req : requests) {
+ anyRebuild = anyRebuild || req.rebuild;
+ mergedInfos.addAll(req.dhcpInfos);
+ }
+
+ logger.debug(String.format("Coalesced %d DHCP apply requests for host[uuid:%s]", requests.size(), hostUuid));
+
+ applyDhcpToHosts(mergedInfos, hostUuid, anyRebuild, completion);
+ }
+ }
+
+ private final DhcpApplyQueue dhcpApplyCoalesceQueue = new DhcpApplyQueue();
+
public static final String APPLY_DHCP_PATH = "/flatnetworkprovider/dhcp/apply";
public static final String BATCH_APPLY_DHCP_PATH = "/flatnetworkprovider/dhcp/batchApply";
public static final String PREPARE_DHCP_PATH = "/flatnetworkprovider/dhcp/prepare";
@@ -1991,7 +2032,7 @@ public DhcpInfo call(DhcpStruct arg) {
List dns = new ArrayList<>();
List dns6 = new ArrayList<>();
- for (String dnsIp : nwServiceMgr.getL3NetworkDns(arg.getL3Network().getUuid())) {
+ for (String dnsIp : nwServiceMgr.getVmNicDns(arg.getVmUuid(), arg.getL3Network().getUuid())) {
if (NetworkUtils.isIpv4Address(dnsIp)) {
dns.add(dnsIp);
} else {
@@ -2074,7 +2115,9 @@ public void applyDhcpService(List dhcpStructList, VmInstanceSpec spe
return;
}
- applyDhcpToHosts(toDhcpInfo(dhcpStructList), spec.getDestHost().getUuid(), false, completion);
+ String hostUuid = spec.getDestHost().getUuid();
+ DhcpApplyRequest request = new DhcpApplyRequest(hostUuid, toDhcpInfo(dhcpStructList), false);
+ dhcpApplyCoalesceQueue.submit(hostUuid, request, completion);
}
private void releaseDhcpService(List info, final String vmUuid, final String hostUuid, final NoErrorCompletion completion) {
diff --git a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatEipBackend.java b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatEipBackend.java
index 5f0b1fb23e7..38a88a84950 100755
--- a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatEipBackend.java
+++ b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatEipBackend.java
@@ -439,8 +439,15 @@ public EipTO call(EipVO eip) {
to.nicIp = ip.getIp();
to.nicGateway = ip.getGateway();
to.nicNetmask = ip.getNetmask();
- NormalIpRangeVO ipr = Q.New(NormalIpRangeVO.class).eq(NormalIpRangeVO_.uuid, ip.getIpRangeUuid()).find();
- to.nicPrefixLen = ipr.getPrefixLen();
+ // First try to use prefixLen from UsedIpVO (for IP outside range)
+ if (ip.getPrefixLen() != null) {
+ to.nicPrefixLen = ip.getPrefixLen();
+ } else if (ip.getIpRangeUuid() != null) {
+ NormalIpRangeVO ipr = Q.New(NormalIpRangeVO.class).eq(NormalIpRangeVO_.uuid, ip.getIpRangeUuid()).find();
+ if (ipr != null) {
+ to.nicPrefixLen = ipr.getPrefixLen();
+ }
+ }
to.vmBridgeName = bridgeNames.get(ip.getL3NetworkUuid());
}
}
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/iscsi/src/main/java/org/zstack/iscsi/kvm/KvmIscsiNodeServer.java b/plugin/iscsi/src/main/java/org/zstack/iscsi/kvm/KvmIscsiNodeServer.java
index 66936e05dc0..0f773d193e7 100644
--- a/plugin/iscsi/src/main/java/org/zstack/iscsi/kvm/KvmIscsiNodeServer.java
+++ b/plugin/iscsi/src/main/java/org/zstack/iscsi/kvm/KvmIscsiNodeServer.java
@@ -20,10 +20,8 @@
import org.zstack.header.host.HostInventory;
import org.zstack.header.host.HostVO;
import org.zstack.header.message.MessageReply;
-import org.zstack.header.storage.addon.primary.BaseVolumeInfo;
-import org.zstack.header.storage.addon.primary.HeartbeatVolumeTO;
-import org.zstack.header.storage.addon.primary.HeartbeatVolumeTopology;
-import org.zstack.header.storage.addon.primary.PrimaryStorageNodeSvc;
+import org.zstack.header.storage.addon.primary.*;
+import org.zstack.storage.addon.primary.ExternalHostIdGetter;
import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.header.vm.VmInstanceMigrateExtensionPoint;
import org.zstack.header.vm.VmInstanceSpec;
@@ -38,6 +36,8 @@
import org.zstack.kvm.*;
import org.zstack.storage.addon.primary.ExternalPrimaryStorageFactory;
import org.zstack.utils.DebugUtils;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
import java.util.ArrayList;
import java.util.List;
@@ -49,6 +49,8 @@
public class KvmIscsiNodeServer implements Component, KVMStartVmExtensionPoint, VmInstanceMigrateExtensionPoint,
KVMConvertVolumeExtensionPoint, KVMDetachVolumeExtensionPoint, KVMAttachVolumeExtensionPoint,
KVMPreAttachIsoExtensionPoint, KvmSetupSelfFencerExtensionPoint {
+ private static final CLogger logger = Utils.getLogger(KvmIscsiNodeServer.class);
+
@Autowired
private ExternalPrimaryStorageFactory extPsFactory;
@@ -235,13 +237,24 @@ public void fail(ErrorCode errorCode) {
@Override
public void run(FlowTrigger trigger, Map data) {
+ ExternalPrimaryStorageHostRefVO ref = Q.New(ExternalPrimaryStorageHostRefVO.class)
+ .eq(ExternalPrimaryStorageHostRefVO_.hostUuid, param.getHostUuid())
+ .eq(ExternalPrimaryStorageHostRefVO_.primaryStorageUuid, param.getPrimaryStorage().getUuid())
+ .find();
+ if (ref == null || ref.getHostId() == 0) {
+ logger.warn(String.format("not found hostId for hostUuid[%s] and primaryStorageUuid[%s]",
+ param.getHostUuid(), param.getPrimaryStorage().getUuid()));
+ ref = new ExternalHostIdGetter(999).getOrAllocateHostIdRef(
+ param.getHostUuid(), param.getPrimaryStorage().getUuid());
+ }
+
KvmSetupSelfFencerCmd cmd = new KvmSetupSelfFencerCmd();
cmd.interval = param.getInterval();
cmd.maxAttempts = param.getMaxAttempts();
cmd.coveringPaths = heartbeatVol.getCoveringPaths();
cmd.heartbeatUrl = heartbeatVol.getInstallPath();
cmd.storageCheckerTimeout = param.getStorageCheckerTimeout();
- cmd.hostId = heartbeatVol.getHostId();
+ cmd.hostId = ref.getHostId();
cmd.heartbeatRequiredSpace = heartbeatVol.getHeartbeatRequiredSpace();
cmd.hostUuid = param.getHostUuid();
cmd.strategy = param.getStrategy();
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
index a8a1378288b..90e576cad7f 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java
@@ -2283,6 +2283,8 @@ public static class StartVmCmd extends vdiCmd implements VmAddOnsCmd {
private boolean isApplianceVm;
@GrayVersion(value = "5.0.0")
private String systemSerialNumber;
+ @GrayVersion(value = "5.5.12")
+ private String guestOsType;
@GrayVersion(value = "5.0.0")
private String bootMode;
// used when bootMode == 'UEFI'
@@ -2312,6 +2314,8 @@ public static class StartVmCmd extends vdiCmd implements VmAddOnsCmd {
private boolean consoleLogToFile;
@GrayVersion(value = "5.0.0")
private boolean acpi;
+ @GrayVersion(value = "5.5.12")
+ private boolean pmu = true;
@GrayVersion(value = "5.0.0")
private boolean x2apic = true;
// cpuid hypervisor feature
@@ -2474,6 +2478,14 @@ public void setSystemSerialNumber(String systemSerialNumber) {
this.systemSerialNumber = systemSerialNumber;
}
+ public String getGuestOsType() {
+ return guestOsType;
+ }
+
+ public void setGuestOsType(String guestOsType) {
+ this.guestOsType = guestOsType;
+ }
+
public String getVmCpuModel() {
return vmCpuModel;
}
@@ -2835,6 +2847,14 @@ public void setAcpi(boolean acpi) {
this.acpi = acpi;
}
+ public boolean isPmu() {
+ return pmu;
+ }
+
+ public void setPmu(boolean pmu) {
+ this.pmu = pmu;
+ }
+
public boolean getX2apic() {
return x2apic;
}
@@ -3805,6 +3825,26 @@ public static class MigrateVmCmd extends AgentCommand implements HasThreadContex
private boolean reload;
@GrayVersion(value = "5.0.0")
private long bandwidth;
+ @GrayVersion(value = "5.5.12")
+ private boolean useTls;
+ @GrayVersion(value = "5.5.12")
+ private String srcHostManagementIp;
+
+ public String getSrcHostManagementIp() {
+ return srcHostManagementIp;
+ }
+
+ public void setSrcHostManagementIp(String srcHostManagementIp) {
+ this.srcHostManagementIp = srcHostManagementIp;
+ }
+
+ public boolean isUseTls() {
+ return useTls;
+ }
+
+ public void setUseTls(boolean useTls) {
+ this.useTls = useTls;
+ }
public Integer getDownTime() {
return downTime;
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 1b2df9f8f2a..314ff983470 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java
@@ -81,6 +81,7 @@ public interface KVMConstant {
String KVM_DELETE_CONSOLE_FIREWALL_PATH = "/vm/console/deletefirewall";
String KVM_UPDATE_HOST_OS_PATH = "/host/updateos";
String KVM_HOST_UPDATE_DEPENDENCY_PATH = "/host/updatedependency";
+
String HOST_SHUTDOWN = "/host/shutdown";
String HOST_REBOOT = "/host/reboot";
String HOST_UPDATE_SPICE_CHANNEL_CONFIG_PATH = "/host/updateSpiceChannelConfig";
@@ -96,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";
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
index f4691e6fb0c..ee765aad40f 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java
@@ -120,6 +120,10 @@ public class KVMGlobalConfig {
@GlobalConfigDef(defaultValue = "false", type = Boolean.class, description = "enable install host shutdown hook")
public static GlobalConfig INSTALL_HOST_SHUTDOWN_HOOK = new GlobalConfig(CATEGORY, "install.host.shutdown.hook");
+ @GlobalConfigValidation(numberGreaterThan = 0)
+ @GlobalConfigDef(defaultValue = "600", type = Long.class, description = "timeout in seconds for orphaned VM skip entries from departed management nodes")
+ public static GlobalConfig ORPHANED_VM_SKIP_TIMEOUT = new GlobalConfig(CATEGORY, "vm.orphanedSkipTimeout");
+
@GlobalConfigValidation(validValues = {"true", "false"})
@GlobalConfigDef(defaultValue = "false", type = Boolean.class, description = "enable memory auto balloon")
@BindResourceConfig({VmInstanceVO.class, HostVO.class, ClusterVO.class})
@@ -135,6 +139,10 @@ public class KVMGlobalConfig {
@BindResourceConfig({HostVO.class, ClusterVO.class})
public static GlobalConfig RECONNECT_HOST_RESTART_LIBVIRTD_SERVICE = new GlobalConfig(CATEGORY, "reconnect.host.restart.libvirtd.service");
+ @GlobalConfigValidation(validValues = {"true", "false"})
+ @GlobalConfigDef(defaultValue = "true", type = Boolean.class, description = "enable TLS encryption for libvirt remote connections (migration)")
+ public static GlobalConfig LIBVIRT_TLS_ENABLED = new GlobalConfig(CATEGORY, "libvirt.tls.enabled");
+
@GlobalConfigValidation
public static GlobalConfig KVMAGENT_PHYSICAL_MEMORY_USAGE_ALARM_THRESHOLD = new GlobalConfig(CATEGORY, "kvmagent.physicalmemory.usage.alarm.threshold");
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
index a245757517d..b485c343d18 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java
@@ -33,6 +33,7 @@
import org.zstack.core.db.SQLBatch;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
+import org.zstack.core.jsonlabel.JsonLabel;
import org.zstack.core.thread.*;
import org.zstack.core.timeout.ApiTimeoutManager;
import org.zstack.core.timeout.TimeHelper;
@@ -66,6 +67,7 @@
import org.zstack.header.message.MessageReply;
import org.zstack.header.message.NeedReplyMessage;
import org.zstack.header.network.l2.*;
+import org.zstack.header.os.OSArchitecture;
import org.zstack.header.network.l3.L3NetworkInventory;
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.rest.JsonAsyncRESTCallback;
@@ -127,6 +129,20 @@ public class KVMHost extends HostBase implements Host {
protected static OperationChecker allowedOperations = new OperationChecker(true);
protected static OperationChecker skipOperations = new OperationChecker(true);
+ public static Set parseSanIps(String sanOutput) {
+ Set sanIps = new HashSet<>();
+ if (sanOutput == null || sanOutput.isEmpty()) {
+ return sanIps;
+ }
+ for (String line : sanOutput.split(",|\n")) {
+ String trimmed = line.trim();
+ if (trimmed.startsWith("IP Address:")) {
+ sanIps.add(trimmed.substring("IP Address:".length()).trim());
+ }
+ }
+ return sanIps;
+ }
+
@Autowired
@Qualifier("KVMHostFactory")
protected KVMHostFactory factory;
@@ -3163,6 +3179,7 @@ public void run(final FlowTrigger trigger, Map data) {
cmd.setDestHostIp(dstHostMigrateIp);
cmd.setSrcHostIp(srcHostMigrateIp);
cmd.setDestHostManagementIp(dstHostMnIp);
+ cmd.setSrcHostManagementIp(srcHostMnIp);
cmd.setMigrateFromDestination(migrateFromDestination);
cmd.setStorageMigrationPolicy(storageMigrationPolicy == null ? null : storageMigrationPolicy.toString());
cmd.setVmUuid(vmUuid);
@@ -3174,6 +3191,8 @@ public void run(final FlowTrigger trigger, Map data) {
cmd.setDownTime(s.downTime);
cmd.setBandwidth(s.bandwidth);
cmd.setNics(nicTos);
+ cmd.setUseTls(KVMGlobalConfig.LIBVIRT_TLS_ENABLED.value(Boolean.class)
+ && rcf.getResourceConfigValue(KVMGlobalConfig.RECONNECT_HOST_RESTART_LIBVIRTD_SERVICE, self.getUuid(), Boolean.class));
if (s.diskMigrationMap != null) {
Map diskMigrationMap = new HashMap<>();
@@ -4432,6 +4451,7 @@ protected void startVm(final VmInstanceSpec spec, final NeedReplyMessage msg, fi
cmd.setAdditionalQmp(VmGlobalConfig.ADDITIONAL_QMP.value(Boolean.class));
cmd.setApplianceVm(spec.getVmInventory().getType().equals("ApplianceVm"));
cmd.setSystemSerialNumber(makeAndSaveVmSystemSerialNumber(spec.getVmInventory().getUuid()));
+ cmd.setGuestOsType(spec.getVmInventory().getGuestOsType());
if (!NetworkGlobalProperty.CHASSIS_ASSET_TAG.isEmpty()) {
cmd.setChassisAssetTag(NetworkGlobalProperty.CHASSIS_ASSET_TAG);
}
@@ -4570,6 +4590,14 @@ protected void startVm(final VmInstanceSpec spec, final NeedReplyMessage msg, fi
cmd.setCreatePaused(true);
}
cmd.setAcpi(true);
+ // aarch64: disable PMU by default to avoid kernel panic on new Kunpeng-920 (7270Z/5230Z)
+ // where PMMIR_EL1 register is not supported by KVM. See ZSTAC-76375
+ // GlobalConfig vm.pmu defaults to false; users can re-enable via ResourceConfig.
+ if (OSArchitecture.AARCH64.normalizedArchName().equals(architecture)) {
+ Boolean pmuEnabled = rcf.getResourceConfigValue(
+ VmGlobalConfig.VM_PMU, spec.getVmInventory().getUuid(), Boolean.class);
+ cmd.setPmu(Boolean.TRUE.equals(pmuEnabled));
+ }
GuestOsCharacter.Config config = GuestOsHelper.getInstance().getGuestOsCharacter(
spec.getVmInventory().getArchitecture(),
@@ -5570,10 +5598,10 @@ public boolean skip(Map data) {
@Override
public void run(FlowTrigger trigger, Map data) {
+ Ssh ssh = new Ssh().setUsername(getSelf().getUsername())
+ .setPassword(getSelf().getPassword()).setPort(getSelf().getPort())
+ .setHostname(getSelf().getManagementIp());
try {
- Ssh ssh = new Ssh().setUsername(getSelf().getUsername())
- .setPassword(getSelf().getPassword()).setPort(getSelf().getPort())
- .setHostname(getSelf().getManagementIp());
ssh.command(String.format("grep -i ^uuid %s | sed 's/uuid://g'", hostTakeOverFlagPath));
SshResult hostRet = ssh.run();
if (hostRet.isSshFailure() || hostRet.getReturnCode() != 0) {
@@ -5622,6 +5650,8 @@ public void run(FlowTrigger trigger, Map data) {
logger.warn(e.getMessage(), e);
trigger.next();
return;
+ } finally {
+ ssh.close();
}
}
});
@@ -5681,6 +5711,72 @@ public void run(FlowTrigger trigger, Map data) {
}
});
+ flow(new NoRollbackFlow() {
+ String __name__ = "check-tls-certs-if-needed";
+
+ @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);
+ }
+
+ @Override
+ public void run(FlowTrigger trigger, Map data) {
+ // 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();
+
+ 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);
+ }
+ } catch (Exception e) {
+ logger.warn(String.format(
+ "TLS cert detection failed on host[uuid:%s], continue connect flow: %s",
+ self.getUuid(), e.getMessage()), e);
+ }
+
+ trigger.next();
+ }
+ });
+
flow(new NoRollbackFlow() {
String __name__ = "apply-ansible-playbook";
@@ -5815,6 +5911,27 @@ public void run(final FlowTrigger trigger, Map data) {
deployArguments.setSkipPackages(info.getSkipPackages());
deployArguments.setUpdatePackages(String.valueOf(CoreGlobalProperty.UPDATE_PKG_WHEN_CONNECT));
+ String managementIp = getSelf().getManagementIp();
+ String detectedIps = (String) data.get("TLS_DETECTED_IPS");
+ String tlsCertIps = KVMHostUtils.unionTlsCertIps(
+ self.getUuid(), managementIp, detectedIps);
+ deployArguments.setTlsCertIps(tlsCertIps);
+
+ // ZSTAC-84446: force ansible re-run only when policy allows;
+ // see KVMHostUtils#shouldForceTlsRedeploy.
+ Boolean needDeployTlsCert = (Boolean) data.get("NEED_DEPLOY_TLS_CERT");
+ 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()) {
runner.setForceRun(true);
}
@@ -6000,6 +6117,7 @@ public void fail(ErrorCode errorCode) {
flow(createCollectHostFactsFlow(info));
+
if (info.isNewAdded()) {
flow(new NoRollbackFlow() {
String __name__ = "check-qemu-libvirt-version";
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostDeployArguments.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostDeployArguments.java
index 71fb8a9769e..f2bb79c110b 100644
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostDeployArguments.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostDeployArguments.java
@@ -39,6 +39,8 @@ public class KVMHostDeployArguments extends SyncTimeRequestedDeployArguments {
private String restartLibvirtd;
@SerializedName("extra_packages")
private String extraPackages;
+ @SerializedName("tls_cert_ips")
+ private String tlsCertIps;
private transient boolean forceRun = false;
@@ -135,6 +137,14 @@ public void setExtraPackages(String extraPackages) {
this.extraPackages = extraPackages;
}
+ public String getTlsCertIps() {
+ return tlsCertIps;
+ }
+
+ public void setTlsCertIps(String tlsCertIps) {
+ this.tlsCertIps = tlsCertIps;
+ }
+
public String getEnableSpiceTls() {
return enableSpiceTls;
}
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java
index acb129fe6dc..0188c920a83 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java
@@ -1,5 +1,6 @@
package org.zstack.kvm;
+import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.util.UriComponentsBuilder;
@@ -9,6 +10,8 @@
import org.zstack.compute.vm.VmNicManager;
import org.zstack.core.CoreGlobalProperty;
import org.zstack.core.ansible.AnsibleFacade;
+import org.zstack.core.jsonlabel.JsonLabel;
+import org.zstack.core.jsonlabel.JsonLabelInventory;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusListCallBack;
import org.zstack.core.cloudbus.CloudBusSteppingCallback;
@@ -75,6 +78,7 @@
import org.zstack.resourceconfig.ResourceConfigFacade;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.IpRangeSet;
+import org.zstack.utils.ShellUtils;
import org.zstack.utils.SizeUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.data.SizeUnit;
@@ -85,6 +89,7 @@
import org.zstack.utils.logging.CLogger;
import javax.persistence.Tuple;
+import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -122,6 +127,10 @@ public class KVMHostFactory extends AbstractService implements HypervisorFactory
HypervisorMessageFactory {
private static final CLogger logger = Utils.getLogger(KVMHostFactory.class);
+ private static final String LIBVIRT_TLS_CA_KEY = "libvirtTLSCA";
+ private static final String LIBVIRT_TLS_PRIVATE_KEY = "libvirtTLSPrivateKey";
+ private static final String CA_DIR = "/var/lib/zstack/pki/CA";
+
public static final HypervisorType hypervisorType = new HypervisorType(KVMConstant.KVM_HYPERVISOR_TYPE);
public static final VolumeFormat QCOW2_FORMAT = new VolumeFormat(VolumeConstant.VOLUME_FORMAT_QCOW2, hypervisorType);
public static final VolumeFormat RAW_FORMAT = new VolumeFormat(VolumeConstant.VOLUME_FORMAT_RAW, hypervisorType);
@@ -458,8 +467,56 @@ private void processKvmagentPhysicalMemUsageAbnormal(HostProcessPhysicalMemoryUs
bus.send(restartKvmAgentMsg);
}
+ private void initLibvirtTlsCA() {
+ if (CoreGlobalProperty.UNIT_TEST_ON) {
+ return;
+ }
+
+ try {
+ ShellUtils.run(String.format("mkdir -p %s", CA_DIR));
+ ShellUtils.run("chown -R zstack:zstack /var/lib/zstack/pki");
+
+ File caFile = new File(CA_DIR + "/cacert.pem");
+ File keyFile = new File(CA_DIR + "/cakey.pem");
+
+ // Local CA missing — generate with openssl
+ // NOTE: ShellUtils.run() prepends sudo only to the first command in &&-chains,
+ // so each command must be a separate call.
+ if (!caFile.exists() || !keyFile.exists()) {
+ ShellUtils.run(String.format(
+ "openssl genrsa -out %s/cakey.pem 4096", CA_DIR));
+ ShellUtils.run(String.format(
+ "openssl req -new -x509 -days 3650 -key %s/cakey.pem " +
+ "-out %s/cacert.pem -subj '/O=ZStack/CN=ZStack Libvirt CA'",
+ CA_DIR, CA_DIR));
+ ShellUtils.run(String.format("chown zstack:zstack %s/cakey.pem %s/cacert.pem",
+ CA_DIR, CA_DIR));
+ ShellUtils.run(String.format("chmod 600 %s/cakey.pem", CA_DIR));
+ ShellUtils.run(String.format("chmod 644 %s/cacert.pem", CA_DIR));
+ }
+
+ String ca = FileUtils.readFileToString(caFile).trim();
+ String key = FileUtils.readFileToString(keyFile).trim();
+
+ // createIfAbsent: DB has no record → write; DB has record → return DB value
+ JsonLabelInventory caInv = new JsonLabel().createIfAbsent(LIBVIRT_TLS_CA_KEY, ca);
+ JsonLabelInventory keyInv = new JsonLabel().createIfAbsent(LIBVIRT_TLS_PRIVATE_KEY, key);
+
+ // Use DB as source of truth — overwrite local files (HA: MN2 uses MN1's CA from DB)
+ FileUtils.writeStringToFile(caFile, caInv.getLabelValue());
+ FileUtils.writeStringToFile(keyFile, keyInv.getLabelValue());
+ ShellUtils.run(String.format("chmod 600 %s/cakey.pem", CA_DIR));
+ ShellUtils.run(String.format("chmod 644 %s/cacert.pem", CA_DIR));
+
+ logger.info("Libvirt TLS CA initialized and persisted to database");
+ } catch (Exception e) {
+ logger.warn("Failed to initialize libvirt TLS CA", e);
+ }
+ }
+
@Override
public boolean start() {
+ initLibvirtTlsCA();
deployAnsibleModule();
populateExtensions();
configKVMDeviceType();
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..6934d7112d1 100644
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java
@@ -1,6 +1,8 @@
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.tag.SystemTagVO;
@@ -9,9 +11,12 @@
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.Set;
@@ -21,6 +26,135 @@
public class KVMHostUtils {
private static final CLogger logger = CLoggerImpl.getLogger(KVMHostUtils.class);
+ /**
+ * Collect host IPv4 addresses, mirroring zstack-utility host_plugin.fact()
+ * (only filter: ifname endswith "zs", drop 127.0.0.1 / MN VIP).
+ */
+ public static String collectHostIps(SshShell sshShell, String hostUuid, String managementIp) {
+ if (sshShell == null) {
+ return managementIp;
+ }
+ // Run a quote-free command; parse on the MN side to avoid nested-quote
+ // mangling when SshShell wraps the command with single quotes.
+ 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 IP list for ansible deploy: detectedIps ∪ EXTRA_IPS tag.
+ * detectedIps comes from the prior check-tls-certs flow (no second SSH).
+ * If detectedIps is null/empty, fall back to managementIp + EXTRA_IPS.
+ */
+ 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);
+ }
+
+ /** Pure function for unit tests: detectedIps ∪ extraIps, fall back to mgmtIp when detected empty. */
+ public static String unionIps(String detectedIpsCsv, String managementIp, String extraIpsCsv, String mnVip) {
+ 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);
+ }
+ }
+ }
+
+ ips.remove("127.0.0.1");
+ if (mnVip != null) {
+ ips.remove(mnVip);
+ }
+ return String.join(",", ips);
+ }
+
+ /**
+ * Parse the raw output of "ip -4 -o addr show" and build the IP list,
+ * mirroring zstack-utility host_plugin.fact() (filter ifname endswith "zs",
+ * drop 127.0.0.1 / MN VIP). Each line looks like:
+ * "5: zsn0.2000 inet 12.1.251.206/16 brd ... scope global zsn0.2000\..."
+ * Pure function for unit tests.
+ */
+ 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);
+ }
+ }
+ }
+
+ ips.remove("127.0.0.1");
+ if (mnVip != null) {
+ ips.remove(mnVip);
+ }
+ return String.join(",", 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 (RECONNECT_HOST_RESTART_LIBVIRTD_SERVICE) or it's a fresh add
+ * (full-deploy will start libvirtd anyway). Skipping on plain reconnect
+ * keeps kvmagent PID stable.
+ */
+ public static boolean shouldForceTlsRedeploy(boolean needDeployTlsCert,
+ boolean allowRestartLibvirtd,
+ boolean isNewAdded) {
+ if (!needDeployTlsCert) {
+ return false;
+ }
+ return allowRestartLibvirtd || isNewAdded;
+ }
+
+ 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/KvmVmSyncPingTask.java b/plugin/kvm/src/main/java/org/zstack/kvm/KvmVmSyncPingTask.java
index b02525b99d2..f618ab49922 100755
--- a/plugin/kvm/src/main/java/org/zstack/kvm/KvmVmSyncPingTask.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/KvmVmSyncPingTask.java
@@ -69,6 +69,15 @@ public class KvmVmSyncPingTask extends VmTracer implements KVMPingAgentNoFailure
private List skipVmTracerReplies = new ArrayList<>();
private Map vmInShutdownMap = new ConcurrentHashMap<>();
+ // Orphaned skip entries from departed MN nodes. Key=vmUuid, Value=timestamp when orphaned.
+ // These VMs remain in skip-trace state to avoid false HA triggers
+ // when a MN restarts and its in-flight VM operations haven't completed yet. See ZSTAC-80821.
+ private final ConcurrentHashMap orphanedSkipVms = new ConcurrentHashMap<>();
+
+ private long getOrphanTtlMs() {
+ return KVMGlobalConfig.ORPHANED_VM_SKIP_TIMEOUT.value(Long.class) * 1000;
+ }
+
{
getReflections().getTypesAnnotatedWith(SkipVmTracer.class).forEach(clz -> {
skipVmTracerMessages.add(clz.asSubclass(Message.class));
@@ -196,8 +205,13 @@ private void syncVm(final HostInventory host, final Completion completion) {
// Get vms to skip before send command to host to confirm the vm will be skipped after sync command finished.
// The problem is if one vm-sync skipped operation is started and finished during vm sync command's handling
// vm state would still be sync to mn
+ // ZSTAC-80821: clean up expired orphaned entries each sync cycle
+ cleanupExpiredOrphanedSkipVms();
+
Set vmsToSkipSetHostSide = new HashSet<>();
vmsToSkip.values().forEach(vmsToSkipSetHostSide::addAll);
+ // ZSTAC-80821: also skip VMs from departed MN nodes that are still within TTL
+ vmsToSkipSetHostSide.addAll(orphanedSkipVms.keySet());
// if the vm is not running on host when sync command executing but started as soon as possible
// before response handling of vm sync, mgmtSideStates will including the running vm but not result in
@@ -228,6 +242,8 @@ public void run(MessageReply reply) {
// Get vms to skip after sync result returned.
vmsToSkip.values().forEach(vmsToSkipSetHostSide::addAll);
+ // ZSTAC-80821: include orphaned entries from departed MN nodes
+ vmsToSkipSetHostSide.addAll(orphanedSkipVms.keySet());
Collection vmUuidsInDeleteVmGC = DeleteVmGC.queryVmInGC(host.getUuid(), ret.getStates().keySet());
@@ -446,7 +462,19 @@ public void nodeJoin(ManagementNodeInventory inv) {
@Override
public void nodeLeft(ManagementNodeInventory inv) {
vmApis.remove(inv.getUuid());
- vmsToSkip.remove(inv.getUuid());
+
+ // ZSTAC-80821: Instead of immediately removing skip list entries, move them
+ // to the orphaned set with a TTL. This prevents false HA triggers for VMs that
+ // are still being started by kvmagent but whose controlling MN has restarted.
+ Set skippedVms = vmsToSkip.remove(inv.getUuid());
+ if (skippedVms != null && !skippedVms.isEmpty()) {
+ long now = System.currentTimeMillis();
+ for (String vmUuid : skippedVms) {
+ orphanedSkipVms.put(vmUuid, now);
+ logger.info(String.format("moved VM[uuid:%s] from departed MN[uuid:%s] skip list to orphaned set" +
+ " (will expire in %d minutes)", vmUuid, inv.getUuid(), getOrphanTtlMs() / 60000));
+ }
+ }
}
@Override
@@ -460,6 +488,39 @@ public void iJoin(ManagementNodeInventory inv) {
}
public boolean isVmDoNotNeedToTrace(String vmUuid) {
- return vmsToSkip.values().stream().anyMatch(vmsToSkipSet -> vmsToSkipSet.contains(vmUuid));
+ if (vmsToSkip.values().stream().anyMatch(vmsToSkipSet -> vmsToSkipSet.contains(vmUuid))) {
+ return true;
+ }
+
+ // ZSTAC-80821: Also check orphaned skip entries from departed MN nodes
+ Long orphanedAt = orphanedSkipVms.get(vmUuid);
+ if (orphanedAt != null) {
+ if (System.currentTimeMillis() - orphanedAt < getOrphanTtlMs()) {
+ logger.debug(String.format("VM[uuid:%s] is in orphaned skip set, skipping trace", vmUuid));
+ return true;
+ } else {
+ // Expired, clean up
+ orphanedSkipVms.remove(vmUuid, orphanedAt);
+ logger.info(String.format("orphaned skip entry for VM[uuid:%s] expired after %d minutes, resuming trace",
+ vmUuid, getOrphanTtlMs() / 60000));
+ }
+ }
+
+ return false;
+ }
+
+ // Periodically clean up expired orphaned entries. Called from VM sync cycle.
+ private void cleanupExpiredOrphanedSkipVms() {
+ if (orphanedSkipVms.isEmpty()) {
+ return;
+ }
+
+ long now = System.currentTimeMillis();
+ for (Map.Entry entry : orphanedSkipVms.entrySet()) {
+ if (now - entry.getValue() >= getOrphanTtlMs()) {
+ orphanedSkipVms.remove(entry.getKey(), entry.getValue());
+ logger.info(String.format("cleaned up expired orphaned skip entry for VM[uuid:%s]", entry.getKey()));
+ }
+ }
}
}
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/HypervisorMetadataCollectorImpl.java b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/HypervisorMetadataCollectorImpl.java
index 468025b01bd..7118fe89bb4 100644
--- a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/HypervisorMetadataCollectorImpl.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/HypervisorMetadataCollectorImpl.java
@@ -131,7 +131,8 @@ private boolean collectHypervisorMetadata(HypervisorMetadataDefinition definitio
Object platformDistName = properties.get(KEY_PLATFORM_DIST_NAME);
Object platformVersion = properties.get(KEY_PLATFORM_VERSION);
if (platformDistName != null && platformVersion != null) {
- definition.setOsReleaseVersion(String.format("%s %s", platformDistName, platformVersion));
+ definition.setOsReleaseVersion(String.format("%s %s",
+ platformDistName, KvmHypervisorInfoHelper.normalizeOsVersion(platformVersion.toString())));
} else {
return false;
}
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoHelper.java b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoHelper.java
index 4c0b3613024..9ae81bb114c 100644
--- a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoHelper.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoHelper.java
@@ -53,7 +53,7 @@ public static Map collectExpectedHypervisorInfoForHost
}
HostOperationSystem os = hostOsMap.get(hostUuid);
- String osReleaseVersion = String.format("%s %s", os.distribution, os.version);
+ String osReleaseVersion = String.format("%s %s", os.distribution, normalizeOsVersion(os.version));
Pair key = new Pair<>(architecture, osReleaseVersion);
HostOsCategoryVO vo = caches.get(key);
@@ -85,6 +85,30 @@ public static Map collectExpectedHypervisorInfoForHost
return results;
}
+ /**
+ * Strip a leading {@code V} or {@code v} from an OS version string when the
+ * next character is a digit. Some distributions (notably Kylin Linux Advanced
+ * Server) expose {@code VERSION_ID="V10"} via {@code /etc/os-release}, while
+ * the matching DVD metadata script outputs the same release as a plain
+ * {@code 10}. Without normalization the two sides build different
+ * {@code osReleaseVersion} keys (e.g. {@code "kylin V10"} vs {@code "kylin 10"})
+ * and the metadata join silently returns no rows, leaving
+ * {@code matchTargetVersion} null and the host stuck in {@code Unknown}.
+ * See ZSTAC-83682.
+ */
+ public static String normalizeOsVersion(String version) {
+ if (version == null) {
+ return null;
+ }
+ String trimmed = version.trim();
+ if (trimmed.length() > 1
+ && (trimmed.charAt(0) == 'V' || trimmed.charAt(0) == 'v')
+ && Character.isDigit(trimmed.charAt(1))) {
+ return trimmed.substring(1);
+ }
+ return trimmed;
+ }
+
public static HypervisorVersionState isQemuVersionMatched(String v1, String v2) {
if (v1 == null || v2 == null) {
return HypervisorVersionState.Unknown;
diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoManagerImpl.java b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoManagerImpl.java
index f9501fe279e..df330171cf6 100644
--- a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoManagerImpl.java
+++ b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoManagerImpl.java
@@ -275,13 +275,15 @@ private boolean saveMetadataList(List definitions)
@Transactional
protected boolean saveHostOsCategoryList(List categoryVOS) {
+ if (CollectionUtils.isEmpty(categoryVOS)) {
+ logger.warn("no hypervisor metadata collected from DVD, skip refresh to preserve existing metadata");
+ return false;
+ }
+
// refresh all metadata with current management node
SQL.New(KvmHostHypervisorMetadataVO.class)
.eq(KvmHostHypervisorMetadataVO_.managementNodeUuid, Platform.getManagementServerId())
.delete();
- if (CollectionUtils.isEmpty(categoryVOS)) {
- return false;
- }
Set requestArchitectures = categoryVOS.stream()
.map(HostOsCategoryVO::getArchitecture)
diff --git a/plugin/ldap/src/main/java/org/zstack/ldap/LdapUtil.java b/plugin/ldap/src/main/java/org/zstack/ldap/LdapUtil.java
index d386ca2d429..5f4b00f59fb 100644
--- a/plugin/ldap/src/main/java/org/zstack/ldap/LdapUtil.java
+++ b/plugin/ldap/src/main/java/org/zstack/ldap/LdapUtil.java
@@ -657,10 +657,10 @@ public LdapServerVO getLdapServer() {
sq.add(LdapServerVO_.scope, SimpleQuery.Op.EQ, scope);
List ldapServers = sq.list();
if (ldapServers.isEmpty()) {
- throw new CloudRuntimeException("No LDAP/AD server record in database.");
+ throw new OperationFailureException(operr(ORG_ZSTACK_LDAP_10020, "No LDAP/AD server record in database."));
}
if (ldapServers.size() > 1) {
- throw new CloudRuntimeException("More than one LDAP/AD server record in database.");
+ throw new OperationFailureException(operr(ORG_ZSTACK_LDAP_10021, "More than one LDAP/AD server record in database."));
}
return ldapServers.get(0);
}
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 97b88c919c2..f2f8271674b 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
@@ -33,12 +33,14 @@
import org.zstack.header.tag.SystemTagVO_;
import org.zstack.header.vm.VmNicVO;
import org.zstack.header.vm.VmNicVO_;
+import org.zstack.network.l3.IpRangeHelper;
import org.zstack.network.service.vip.VipNetworkServicesRefVO;
import org.zstack.network.service.vip.VipNetworkServicesRefVO_;
import org.zstack.network.service.vip.VipVO;
import org.zstack.network.service.vip.VipVO_;
import org.zstack.tag.PatternedSystemTag;
import org.zstack.tag.TagManager;
+import org.zstack.core.upgrade.UpgradeGlobalConfig;
import org.zstack.utils.*;
import org.zstack.utils.function.ForEachFunction;
import org.zstack.utils.logging.CLogger;
@@ -152,10 +154,22 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti
validate((APIGetCandidateVmNicsForLoadBalancerServerGroupMsg)msg);
} else if (msg instanceof APIChangeLoadBalancerBackendServerMsg) {
validate((APIChangeLoadBalancerBackendServerMsg)msg);
+ } else if (msg instanceof APIDeleteLoadBalancerMsg) {
+ validate((APIDeleteLoadBalancerMsg) msg);
}
return msg;
}
+ private void validate(APIDeleteLoadBalancerMsg msg) {
+ if (UpgradeGlobalConfig.GRAYSCALE_UPGRADE.value(Boolean.class)) {
+ LoadBalancerVO lb = dbf.findByUuid(msg.getUuid(), LoadBalancerVO.class);
+ if (lb != null && lb.getType() == LoadBalancerType.SLB) {
+ throw new ApiMessageInterceptionException(argerr(
+ "cannot delete the standalone load balancer[uuid:%s] during grayscale upgrade", msg.getUuid()));
+ }
+ }
+ }
+
private void validate(APIDeleteAccessControlListMsg msg) {
/*List refs = Q.New(LoadBalancerListenerACLRefVO.class).select(LoadBalancerListenerACLRefVO_.listenerUuid)
.eq(LoadBalancerListenerACLRefVO_.aclUuid, msg.getUuid()).isNull(LoadBalancerListenerACLRefVO_.serverGroupUuid).listValues();
@@ -642,6 +656,30 @@ public void run(String arg) {
q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuid", msg.getListenerUuid());
msg.setLoadBalancerUuid(q.getSingleResult());
+
+ // When the load balancer has a VIP configured, the NIC's corresponding IP must be within L3 CIDR
+ LoadBalancerVO lbVO = dbf.findByUuid(msg.getLoadBalancerUuid(), LoadBalancerVO.class);
+ for (String nicUuid : msg.getVmNicUuids()) {
+ VmNicVO nicVO = dbf.findByUuid(nicUuid, VmNicVO.class);
+ if (nicVO == null) {
+ continue;
+ }
+ for (UsedIpVO usedIpVO : nicVO.getUsedIps()) {
+ if (!IpRangeHelper.isIpOutsideL3NetworkCidr(usedIpVO.getIp(), usedIpVO.getL3NetworkUuid())) {
+ continue;
+ }
+ if (lbVO.getVipUuid() != null && usedIpVO.getIpVersion() == IPv6Constants.IPv4) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10176,
+ "cannot add VM NIC[uuid:%s] with IPv4 address[%s] which is outside L3 network CIDR range to load balancer",
+ nicUuid, usedIpVO.getIp()));
+ }
+ if (lbVO.getIpv6VipUuid() != null && usedIpVO.getIpVersion() == IPv6Constants.IPv6) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10173,
+ "cannot add VM NIC[uuid:%s] with IPv6 address[%s] which is outside L3 network CIDR range to load balancer",
+ nicUuid, usedIpVO.getIp()));
+ }
+ }
+ }
}
private boolean hasTag(APIMessage msg, PatternedSystemTag tag) {
@@ -852,6 +890,14 @@ private void validate(APICreateLoadBalancerListenerMsg msg) {
statusCode = LoadBalancerSystemTags.STATUS_CODE.getTokenByTag(tag,
LoadBalancerSystemTags.STATUS_CODE_TOKEN);
}
+ if (LoadBalancerSystemTags.HTTP_COMPRESS_ALGOS.isMatch(tag)) {
+ String compressAlgos = LoadBalancerSystemTags.HTTP_COMPRESS_ALGOS.getTokenByTag(tag,
+ LoadBalancerSystemTags.HTTP_COMPRESS_ALGOS_TOKEN);
+ if (DisableLbSupportHttpCompressAlgos.equals(compressAlgos)) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10172,
+ "could not create the loadbalancer listener with systemTag httpCompressAlgos::disable, please remove this tag"));
+ }
+ }
}
if ((redirectPort != null || statusCode != null) && (httpRedirectHttps == null || HttpRedirectHttps.disable.toString().equals(httpRedirectHttps))) {
@@ -1565,6 +1611,31 @@ private void validate(APIAddBackendServerToServerGroupMsg msg){
}
}
+ // When server group has an IP version, the vmnic's corresponding IP must be within L3 CIDR
+ if (groupVO.getIpVersion() != null) {
+ for (String nicUuid : vmNicUuids) {
+ VmNicVO nicVO = dbf.findByUuid(nicUuid, VmNicVO.class);
+ if (nicVO == null) {
+ continue;
+ }
+ for (UsedIpVO usedIpVO : nicVO.getUsedIps()) {
+ if (!IpRangeHelper.isIpOutsideL3NetworkCidr(usedIpVO.getIp(), usedIpVO.getL3NetworkUuid())) {
+ continue;
+ }
+ if (groupVO.getIpVersion() == IPv6Constants.IPv4 && usedIpVO.getIpVersion() == IPv6Constants.IPv4) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10174,
+ "cannot add VM NIC[uuid:%s] with IPv4 address[%s] which is outside L3 network CIDR range to server group[uuid:%s]",
+ nicUuid, usedIpVO.getIp(), msg.getServerGroupUuid()));
+ }
+ if (groupVO.getIpVersion() == IPv6Constants.IPv6 && usedIpVO.getIpVersion() == IPv6Constants.IPv6) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_LB_10175,
+ "cannot add VM NIC[uuid:%s] with IPv6 address[%s] which is outside L3 network CIDR range to server group[uuid:%s]",
+ nicUuid, usedIpVO.getIp(), msg.getServerGroupUuid()));
+ }
+ }
+ }
+ }
+
canAddVmNic = true;
}
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 f51ed2efb25..dadec74b6ec 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
@@ -43,6 +43,7 @@
import org.zstack.header.vm.*;
import org.zstack.header.vo.ResourceVO;
import org.zstack.identity.Account;
+import org.zstack.network.l3.IpRangeHelper;
import org.zstack.network.l3.L3NetworkManager;
import org.zstack.network.service.vip.*;
import org.zstack.tag.PatternedSystemTag;
@@ -635,6 +636,14 @@ private void handle(APIGetCandidateVmNicsForLoadBalancerServerGroupMsg msg) {
ipVersion = groupVO.getIpVersion();
}
List nicVOS = f.getAttachableVmNicsForServerGroup(self, groupVO, ipVersion);
+
+ // Filter out NICs whose primary IP is outside L3 CIDR
+ nicVOS = nicVOS.stream().filter(nic -> {
+ String nicIp = nic.getIp();
+ String nicL3 = nic.getL3NetworkUuid();
+ return nicIp == null || IpRangeHelper.isIpInL3NetworkCidr(nicIp, nicL3);
+ }).collect(Collectors.toList());
+
reply.setInventories(VmNicInventory.valueOf(nicVOS));
bus.reply(msg, reply);
}
@@ -1046,6 +1055,13 @@ protected void scripts() {
.filter(nic -> !listenerVO.getAttachedVmNics().contains(nic.getUuid()))
.collect(Collectors.toList());
+ // Filter out NICs whose primary IP is outside L3 CIDR
+ nics = nics.stream().filter(nic -> {
+ String nicIp = nic.getIp();
+ String nicL3 = nic.getL3NetworkUuid();
+ return nicIp == null || IpRangeHelper.isIpInL3NetworkCidr(nicIp, nicL3);
+ }).collect(Collectors.toList());
+
reply.setInventories(callGetCandidateVmNicsForLoadBalancerExtensionPoint(msg, VmNicInventory.valueOf(nics)));
}
}.execute();
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java
index b72673df10a..785b4092144 100755
--- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java
@@ -385,7 +385,12 @@ private String getHostUuidFromAllocateMsg(AllocatePrimaryStorageSpaceMsg msg) {
throw new OperationFailureException(
argerr(ORG_ZSTACK_STORAGE_PRIMARY_LOCAL_10023, "invalid uri, correct example is file://$URL;hostUuid://$HOSTUUID or volume://$VOLUMEUUID "));
}
- hostUuid = uriParsers.get(protocol).parseUri(msg.getRequiredInstallUri()).hostUuid;
+ AbstractUriParser parser = uriParsers.get(protocol);
+ if (parser == null) {
+ throw new OperationFailureException(
+ argerr(ORG_ZSTACK_STORAGE_PRIMARY_LOCAL_10023, "unsupported protocol[%s] in uri[%s]", protocol, msg.getRequiredInstallUri()));
+ }
+ hostUuid = parser.parseUri(msg.getRequiredInstallUri()).hostUuid;
}
if (hostUuid != null) {
diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java
index 3b78ffab51b..ec0b51711ba 100755
--- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java
+++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageKvmBackend.java
@@ -3797,7 +3797,8 @@ public void success(CheckInitializedFileRsp rsp) {
@Override
public void fail(ErrorCode errorCode) {
- completion.fail(operr(ORG_ZSTACK_STORAGE_PRIMARY_LOCAL_10081, "cannot find flag file [%s] on host [%s], because: %s", makeInitializedFilePath(), hostUuid, errorCode.getCause().getDetails()));
+ String causeDetails = errorCode.getCause() != null ? errorCode.getCause().getDetails() : errorCode.getDetails();
+ completion.fail(operr(ORG_ZSTACK_STORAGE_PRIMARY_LOCAL_10081, "cannot find flag file [%s] on host [%s], because: %s", makeInitializedFilePath(), hostUuid, causeDetails));
}
});
}
@@ -3818,7 +3819,8 @@ public void success(AgentResponse rsp) {
@Override
public void fail(ErrorCode errorCode) {
- completion.fail(operr(ORG_ZSTACK_STORAGE_PRIMARY_LOCAL_10082, "cannot create flag file [%s] on host [%s], because: %s", makeInitializedFilePath(), hostUuid, errorCode.getCause().getDetails()));
+ String causeDetails = errorCode.getCause() != null ? errorCode.getCause().getDetails() : errorCode.getDetails();
+ completion.fail(operr(ORG_ZSTACK_STORAGE_PRIMARY_LOCAL_10082, "cannot create flag file [%s] on host [%s], because: %s", makeInitializedFilePath(), hostUuid, causeDetails));
}
});
}
diff --git a/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingApiInterceptor.java b/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingApiInterceptor.java
index 43a2a4d10e7..5316be0629b 100755
--- a/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingApiInterceptor.java
+++ b/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingApiInterceptor.java
@@ -19,6 +19,8 @@
import org.zstack.header.vm.VmInstanceVO_;
import org.zstack.header.vm.VmNicVO;
import org.zstack.header.vm.VmNicVO_;
+import org.zstack.header.network.l3.UsedIpVO;
+import org.zstack.network.l3.IpRangeHelper;
import org.zstack.network.service.vip.*;
import org.zstack.utils.VipUseForList;
import org.zstack.utils.network.IPv6Constants;
@@ -147,6 +149,17 @@ public VipVO call() {
} catch (CloudRuntimeException e) {
throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10011, e.getMessage()));
}
+
+ // Check if the NIC's IP is outside L3 CIDR range
+ VmNicVO nicVO = dbf.findByUuid(msg.getVmNicUuid(), VmNicVO.class);
+ if (nicVO != null && nicVO.getUsedIpUuid() != null) {
+ UsedIpVO usedIpVO = dbf.findByUuid(nicVO.getUsedIpUuid(), UsedIpVO.class);
+ if (usedIpVO != null && IpRangeHelper.isIpOutsideL3NetworkCidr(usedIpVO.getIp(), usedIpVO.getL3NetworkUuid())) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10025,
+ "cannot bind port forwarding rule to IP address[%s] which is outside L3 network CIDR range",
+ usedIpVO.getIp()));
+ }
+ }
}
private boolean rangeOverlap(int s1, int e1, int s2, int e2) {
@@ -243,6 +256,17 @@ private void validate(APICreatePortForwardingRuleMsg msg) {
checkIfAnotherVip(msg.getVipUuid(), msg.getVmNicUuid());
checkForConflictsWithOtherRules(msg.getVmNicUuid(), msg.getPrivatePortStart(), msg.getPrivatePortEnd(),
msg.getAllowedCidr(), PortForwardingProtocolType.valueOf(msg.getProtocolType()));
+
+ // Check if the NIC's IP is outside L3 CIDR range
+ VmNicVO nicVO = dbf.findByUuid(msg.getVmNicUuid(), VmNicVO.class);
+ if (nicVO != null && nicVO.getUsedIpUuid() != null) {
+ UsedIpVO usedIpVO = dbf.findByUuid(nicVO.getUsedIpUuid(), UsedIpVO.class);
+ if (usedIpVO != null && IpRangeHelper.isIpOutsideL3NetworkCidr(usedIpVO.getIp(), usedIpVO.getL3NetworkUuid())) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10025,
+ "cannot bind port forwarding rule to IP address[%s] which is outside L3 network CIDR range",
+ usedIpVO.getIp()));
+ }
+ }
}
if(msg.getAllowedCidr() != null){
diff --git a/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java b/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java
index ce34e098021..80aa7597ff1 100755
--- a/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java
+++ b/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java
@@ -37,6 +37,7 @@
import org.zstack.header.query.ExpandedQueryStruct;
import org.zstack.header.vm.*;
import org.zstack.identity.AccountManager;
+import org.zstack.network.l3.IpRangeHelper;
import org.zstack.network.l3.L3NetworkManager;
import org.zstack.network.service.NetworkServiceManager;
import org.zstack.network.service.vip.*;
@@ -365,7 +366,16 @@ protected List scripts() {
/* TODO: only ipv4 portforwarding is supported */
List nicInvs = VmNicInventory.valueOf(nics.stream().filter(nic -> !usedVm.contains(nic.getVmInstanceUuid())).collect(Collectors.toList()));
- return l3Mgr.filterVmNicByIpVersion(nicInvs, IPv6Constants.IPv4);
+ List filtered = l3Mgr.filterVmNicByIpVersion(nicInvs, IPv6Constants.IPv4);
+
+ // Filter out NICs whose primary IP is outside L3 CIDR
+ filtered = filtered.stream().filter(nic -> {
+ String nicIp = nic.getIp();
+ String nicL3 = nic.getL3NetworkUuid();
+ return nicIp == null || IpRangeHelper.isIpInL3NetworkCidr(nicIp, nicL3);
+ }).collect(Collectors.toList());
+
+ return filtered;
}
}.execute();
}
diff --git a/plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/SecurityGroupApiInterceptor.java b/plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/SecurityGroupApiInterceptor.java
index 135ad3886aa..d76b88610b1 100755
--- a/plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/SecurityGroupApiInterceptor.java
+++ b/plugin/securityGroup/src/main/java/org/zstack/network/securitygroup/SecurityGroupApiInterceptor.java
@@ -343,14 +343,6 @@ private void validate(APISetVmNicSecurityGroupMsg msg) {
if (!aoMap.isEmpty()) {
Integer[] priorities = aoMap.keySet().toArray(new Integer[aoMap.size()]);
Arrays.sort(priorities);
- if (priorities[0] != 1) {
- throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10022, "could no set vm nic security group, because invalid priority, priority expects to start at 1, but [%d]", priorities[0]));
- }
- for (int i = 0; i < priorities.length - 1; i++) {
- if (priorities[i] + 1 != priorities[i + 1]) {
- throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10023, "could no set vm nic security group, because invalid priority, priority[%d] and priority[%d] expected to be consecutive", priorities[i], priorities[i + 1]));
- }
- }
}
@@ -386,19 +378,6 @@ private void validate(APISetVmNicSecurityGroupMsg msg) {
msg.setRefs(newAOs);
}
- } else {
- if (!adminIntegers.isEmpty()) {
- Integer[] priorities = adminIntegers.toArray(new Integer[adminIntegers.size()]);
- Arrays.sort(priorities);
- if (priorities[0] != 1) {
- throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10024, "could no set vm nic security group, because admin security group priority[%d] must be higher than users", priorities[0]));
- }
- for (int i = 0; i < priorities.length - 1; i++) {
- if (priorities[i] + 1 != priorities[i + 1]) {
- throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10025, "could no set vm nic security group, because admin security group priority[%d] must be higher than users", priorities[i + 1]));
- }
- }
- }
}
}
@@ -498,8 +477,9 @@ private void validate(APIUpdateSecurityGroupRulePriorityMsg msg) {
rvos.stream().filter(rvo -> rvo.getUuid().equals(ao.getRuleUuid())).findFirst().orElseThrow(() ->
new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10041, "could not update security group rule priority, because rule[uuid:%s] not in security group[uuid:%s]", ao.getRuleUuid(), msg.getSecurityGroupUuid())));
- rvos.stream().filter(rvo -> rvo.getPriority() == ao.getPriority()).findFirst().orElseThrow(() ->
- new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10042, "could not update security group rule priority, because priority[%d] not in security group[uuid:%s]", ao.getPriority(), msg.getSecurityGroupUuid())));
+ if (ao.getPriority() < 1 || ao.getPriority() > SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10042, "could not update security group rule priority, because priority[%d] is out of valid range [1, %d]", ao.getPriority(), SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)));
+ }
}
List uuidList = new ArrayList<>(priorityMap.values());
@@ -534,8 +514,8 @@ private void validate(APIChangeSecurityGroupRuleMsg msg) {
if (count.intValue() > SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10047, "could not change security group rule, because security group %s rules number[%d] is out of max limit[%d]", vo.getType(), count.intValue(), SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)));
}
- if (msg.getPriority() > count.intValue()) {
- throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10048, "could not change security group rule, because the maximum priority of %s rule is [%d]", vo.getType().toString(), count.intValue()));
+ if (msg.getPriority() > SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10048, "could not change security group rule, because the maximum priority of %s rule is [%d]", vo.getType().toString(), SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)));
}
if (msg.getPriority() < 0) {
msg.setPriority(SecurityGroupConstant.LOWEST_RULE_PRIORITY);
@@ -1198,11 +1178,11 @@ private void validate(APIAddSecurityGroupRuleMsg msg) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10119, "could not add security group rule, because security group %s rules number[%d] is out of max limit[%d]",
SecurityGroupRuleType.Egress, (egressRuleCount + toCreateEgressRuleCount), SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)));
}
- if (msg.getPriority() > (ingressRuleCount + 1) && toCreateIngressRuleCount > 0) {
- throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10120, "could not add security group rule, because priority[%d] must be consecutive, the ingress rule maximum priority is [%d]", msg.getPriority(), ingressRuleCount));
+ if (msg.getPriority() > SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class) && toCreateIngressRuleCount > 0) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10120, "could not add security group rule, because priority[%d] exceeds the maximum allowed priority [%d]", msg.getPriority(), SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)));
}
- if (msg.getPriority() > (egressRuleCount + 1) && toCreateEgressRuleCount > 0) {
- throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10121, "could not add security group rule, because priority[%d] must be consecutive, the egress rule maximum priority is [%d]", msg.getPriority(), egressRuleCount));
+ if (msg.getPriority() > SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class) && toCreateEgressRuleCount > 0) {
+ throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SECURITYGROUP_10121, "could not add security group rule, because priority[%d] exceeds the maximum allowed priority [%d]", msg.getPriority(), SecurityGroupGlobalConfig.SECURITY_GROUP_RULES_NUM_LIMIT.value(Integer.class)));
}
}
diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java
index 610331e5f6c..a876ee24a34 100755
--- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java
+++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java
@@ -2092,6 +2092,9 @@ void applianceVmsDeleteIpByIpRanges(List applianceVmVOS,
vo = dbf.findByUuid(vo.getUuid(), ApplianceVmVO.class);
for (VmNicVO nic : vo.getVmNics()) {
for (UsedIpVO ip : nic.getUsedIps()) {
+ if (ip.getIpRangeUuid() == null) {
+ continue;
+ }
if (ip.getIpVersion() == IPv6Constants.IPv4 && ipv4RangeUuids.contains(ip.getIpRangeUuid())) {
ReturnIpMsg rmsg = new ReturnIpMsg();
rmsg.setL3NetworkUuid(ip.getL3NetworkUuid());
@@ -2139,7 +2142,7 @@ public List applianceVmsToDeleteNicByIpRanges(List appli
for (ApplianceVmVO vo : applianceVmVOS) {
for (VmNicVO nic : vo.getVmNics()) {
for (UsedIpVO ip : nic.getUsedIps()) {
- if (!iprUuids.contains(ip.getIpRangeUuid())) {
+ if (ip.getIpRangeUuid() == null || !iprUuids.contains(ip.getIpRangeUuid())) {
continue;
}
@@ -2172,7 +2175,7 @@ public List applianceVmsToBeDeletedByIpRanges(List
}
/* if any ip of the nic is deleted, delete the appliance vm */
- if (nic.getUsedIps().stream().anyMatch(ip -> iprUuids.contains(ip.getIpRangeUuid()))) {
+ if (nic.getUsedIps().stream().anyMatch(ip -> ip.getIpRangeUuid() != null && iprUuids.contains(ip.getIpRangeUuid()))) {
toDeleted.add(vos);
break;
}
diff --git a/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageFactory.java b/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageFactory.java
index 70d33ea08d0..c3f98670bb6 100644
--- a/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageFactory.java
+++ b/plugin/xinfini/src/main/java/org/zstack/xinfini/XInfiniStorageFactory.java
@@ -11,6 +11,7 @@
import org.zstack.header.xinfini.XInfiniConstants;
import org.zstack.storage.addon.primary.ExternalPrimaryStorageFactory;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -55,7 +56,7 @@ public String getIdentity() {
@Override
public List getPreferBackupStorageTypes() {
- return preferBackupStorageTypes;
+ return new ArrayList<>(preferBackupStorageTypes);
}
public void setPreferBackupStorageTypes(List preferBackupStorageTypes) {
diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java
index db06239acb3..0d71b507b14 100644
--- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java
+++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java
@@ -179,7 +179,10 @@ public List getActiveClients(String installPath, String prot
if (VolumeProtocol.CBD.toString().equals(protocol)) {
GetVolumeClientsCmd cmd = new GetVolumeClientsCmd();
cmd.setPath(installPath);
- GetVolumeClientsRsp rsp = syncHttpCall(GET_VOLUME_CLIENTS_PATH, cmd, GetVolumeClientsRsp.class);
+ GetVolumeClientsRsp rsp = new HttpCaller<>(GET_VOLUME_CLIENTS_PATH, cmd, GetVolumeClientsRsp.class,
+ null, TimeUnit.SECONDS, 30, true)
+ .setTryNext(true)
+ .syncCall();
List clients = new ArrayList<>();
if (!rsp.isSuccess()) {
@@ -536,6 +539,7 @@ public void handle(Map data) {
error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
+ syncMdsStatuses(newAddonInfo);
completion.fail(errCode);
}
});
@@ -1358,6 +1362,24 @@ public void syncConfig(String config) {
this.config = StringUtils.isEmpty(config) ? new Config() : JSONObjectUtil.toObject(config, Config.class);
}
+ private void syncMdsStatuses(AddonInfo newAddonInfo) {
+ if (addonInfo == null || newAddonInfo == null) {
+ return;
+ }
+
+ for (MdsInfo newMds : newAddonInfo.getMdsInfos()) {
+ for (MdsInfo existMds : addonInfo.getMdsInfos()) {
+ if (existMds.getAddr().equals(newMds.getAddr())) {
+ existMds.setStatus(newMds.getStatus());
+ }
+ }
+ }
+
+ SQL.New(ExternalPrimaryStorageVO.class).eq(ExternalPrimaryStorageVO_.uuid, self.getUuid())
+ .set(ExternalPrimaryStorageVO_.addonInfo, JSONObjectUtil.toJsonString(addonInfo))
+ .update();
+ }
+
@Deprecated
private void reloadDbInfo() {
self = dbf.reload(self);
@@ -1411,6 +1433,11 @@ public class HttpCaller {
private boolean tryNext = false;
+ HttpCaller setTryNext(boolean tryNext) {
+ this.tryNext = tryNext;
+ return this;
+ }
+
public HttpCaller(String path, AgentCommand cmd, Class retClass, ReturnValueCompletion callback) {
this(path, cmd, retClass, callback, null, 0, false);
}
diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageFactory.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageFactory.java
index 5b7c491814e..9df6cd4c97a 100644
--- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageFactory.java
+++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageFactory.java
@@ -11,6 +11,7 @@
import org.zstack.utils.ssh.Ssh;
import org.zstack.utils.ssh.SshResult;
+import java.util.ArrayList;
import java.util.List;
import static org.zstack.core.Platform.operr;
@@ -93,7 +94,7 @@ public void setPreferBackupStorageTypes(List preferBackupStorageTypes) {
@Override
public List getPreferBackupStorageTypes() {
- return preferBackupStorageTypes;
+ return new ArrayList<>(preferBackupStorageTypes);
}
@Override
diff --git a/pom.xml b/pom.xml
index 5173ad0aef2..91f3c4a397a 100755
--- a/pom.xml
+++ b/pom.xml
@@ -550,17 +550,11 @@
opentelemetry-context
1.35.0