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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ envDSLTree
test/zstack-integration-test-result/
premium/test-premium/zstack-api.log
**/bin/
CLAUDE.md
188 changes: 162 additions & 26 deletions compute/src/main/java/org/zstack/compute/vm/StaticIpOperator.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -300,19 +300,19 @@ protected List<DetachNicFromVmMsg> handleDeletionForIpRange(List<VmDeletionStruc
}

/* both ipv4 and ipv6 has been deleted or deleting, then delete nic */
if ((ip4 == null || uuids.contains(ip4.getIpRangeUuid()))
&& (ip6 == null || uuids.contains(ip6.getIpRangeUuid()))) {
if ((ip4 == null || (ip4.getIpRangeUuid() != null && uuids.contains(ip4.getIpRangeUuid())))
&& (ip6 == null || (ip6.getIpRangeUuid() != null && uuids.contains(ip6.getIpRangeUuid())))) {
DetachNicFromVmMsg msg = new DetachNicFromVmMsg();
msg.setVmInstanceUuid(nic.getVmInstanceUuid());
msg.setVmNicUuid(nic.getUuid());
bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vm.getInventory().getUuid());
msgs.add(msg);
} else {
boolean deleteIp4 = false, hasIp6 = false;
if (ip4 != null && uuids.contains(ip4.getIpRangeUuid())) {
if (ip4 != null && ip4.getIpRangeUuid() != null && uuids.contains(ip4.getIpRangeUuid())) {
deleteIp4 = true;
}
if (ip6 != null && !uuids.contains(ip6.getIpRangeUuid())) {
if (ip6 != null && (ip6.getIpRangeUuid() == null || !uuids.contains(ip6.getIpRangeUuid()))) {
hasIp6 = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ public class VmGlobalConfig {
@GlobalConfigValidation(validValues = {"true", "false"})
public static GlobalConfig ENABLE_VM_INTERNAL_IP_OVERWRITE = new GlobalConfig(CATEGORY, "enable.vm.internal.ip.overwrite");

@GlobalConfigValidation(validValues = {"true", "false"})
@GlobalConfigDef(defaultValue = "true", type = Boolean.class, description = "Allow VM NIC to use IP address outside of L3 network IP ranges. When enabled, users must provide netmask/gateway for IPv4 or prefixLen/gateway for IPv6.")
public static GlobalConfig ALLOW_IP_OUTSIDE_RANGE = new GlobalConfig(CATEGORY, "allow.ip.outside.range");

@GlobalConfigValidation(validValues = {"true", "false"})
public static GlobalConfig UNIQUE_VM_NAME = new GlobalConfig(CATEGORY, "uniqueVmName");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@
import org.zstack.utils.network.IPv6Constants;
import org.zstack.utils.network.IPv6NetworkUtils;
import org.zstack.utils.network.NetworkUtils;
import org.zstack.utils.network.NicIpAddressInfo;

import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.*;
Expand Down Expand Up @@ -288,7 +286,7 @@ private void validate(APIChangeVmNicNetworkMsg msg) {

new StaticIpOperator().validateSystemTagInApiMessage(msg);
Map<String, List<String>> staticIps = new StaticIpOperator().getStaticIpbySystemTag(msg.getSystemTags());
if (msg.getRequiredIpMap() != null) {
if (msg.getStaticIp() != null) {
staticIps.computeIfAbsent(msg.getDestL3NetworkUuid(), k -> new ArrayList<>()).add(msg.getStaticIp());
SimpleQuery<NormalIpRangeVO> iprq = dbf.createQuery(NormalIpRangeVO.class);
iprq.add(NormalIpRangeVO_.l3NetworkUuid, Op.EQ, msg.getDestL3NetworkUuid());
Expand Down Expand Up @@ -358,8 +356,8 @@ private void validate(APIChangeVmNicNetworkMsg msg) {
}

SimpleQuery<UsedIpVO> uq = dbf.createQuery(UsedIpVO.class);
uq.add(UsedIpVO_.l3NetworkUuid, Op.EQ, msg.getDestL3NetworkUuid());
uq.add(UsedIpVO_.ip, Op.EQ, msg.getStaticIp());
uq.add(UsedIpVO_.l3NetworkUuid, Op.EQ, l3Uuid);
uq.add(UsedIpVO_.ip, Op.EQ, staticIp);
if (uq.isExists()) {
throw new ApiMessageInterceptionException(operr(ORG_ZSTACK_COMPUTE_VM_10108, "the static IP[%s] has been occupied on the L3 network[uuid:%s]", staticIp, l3Uuid));
}
Expand All @@ -372,15 +370,11 @@ private void validate(APIChangeVmNicNetworkMsg msg) {
msg.getRequiredIpMap().put(e.getKey(), e.getValue());
}

final Map<String, NicIpAddressInfo> nicNetworkInfo = new StaticIpOperator().getNicNetworkInfoBySystemTag(msg.getSystemTags());
NicIpAddressInfo nicIpAddressInfo = nicNetworkInfo.get(msg.getDestL3NetworkUuid());
if (nicIpAddressInfo != null) {
if (!nicIpAddressInfo.ipv4Address.isEmpty() && Q.New(UsedIpVO.class).eq(UsedIpVO_.ip, nicIpAddressInfo.ipv4Address).eq(UsedIpVO_.l3NetworkUuid, msg.getDestL3NetworkUuid()).isExists()) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10109, "the static IP[%s] has been occupied on the L3 network[uuid:%s]", nicIpAddressInfo.ipv4Address, msg.getDestL3NetworkUuid()));
}
if (!nicIpAddressInfo.ipv6Address.isEmpty() && Q.New(UsedIpVO.class).eq(UsedIpVO_.ip, IPv6NetworkUtils.getIpv6AddressCanonicalString(nicIpAddressInfo.ipv6Address)).eq(UsedIpVO_.l3NetworkUuid, msg.getDestL3NetworkUuid()).isExists()) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10110, "the static IP[%s] has been occupied on the L3 network[uuid:%s]", nicIpAddressInfo.ipv6Address, msg.getDestL3NetworkUuid()));
}
if (msg.getIp() != null && !msg.getIp().isEmpty() && Q.New(UsedIpVO.class).eq(UsedIpVO_.ip, msg.getIp()).eq(UsedIpVO_.l3NetworkUuid, msg.getDestL3NetworkUuid()).isExists()) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10109, "the static IP[%s] has been occupied on the L3 network[uuid:%s]", msg.getIp(), msg.getDestL3NetworkUuid()));
}
if (msg.getIp6() != null && !msg.getIp6().isEmpty() && Q.New(UsedIpVO.class).eq(UsedIpVO_.ip, IPv6NetworkUtils.getIpv6AddressCanonicalString(msg.getIp6())).eq(UsedIpVO_.l3NetworkUuid, msg.getDestL3NetworkUuid()).isExists()) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10110, "the static IP[%s] has been occupied on the L3 network[uuid:%s]", msg.getIp6(), msg.getDestL3NetworkUuid()));
}
}

Expand Down Expand Up @@ -588,8 +582,12 @@ private void validateStaticIPv4(VmNicVO vmNicVO, L3NetworkVO l3NetworkVO, String
continue;
}
// check if the ip is in the ip range when ipam is enabled
if (ipVo.getIpRangeUuid() == null) {
// IP is outside range, skip range validation
continue;
}
NormalIpRangeVO rangeVO = dbf.findByUuid(ipVo.getIpRangeUuid(), NormalIpRangeVO.class);
if (!NetworkUtils.isIpv4InCidr(ip, rangeVO.getNetworkCidr())) {
if (rangeVO != null && !NetworkUtils.isIpv4InCidr(ip, rangeVO.getNetworkCidr())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10131, "ip address [%s] is not in ip range [%s]",
ip, rangeVO.getNetworkCidr()));
}
Expand All @@ -615,8 +613,12 @@ private void validateStaticIPv6(VmNicVO vmNicVO, L3NetworkVO l3NetworkVO, String
if (!l3NetworkVO.enableIpAddressAllocation()) {
continue;
}
if (ipVo.getIpRangeUuid() == null) {
// IP is outside range, skip range validation
continue;
}
NormalIpRangeVO rangeVO = dbf.findByUuid(ipVo.getIpRangeUuid(), NormalIpRangeVO.class);
if (!IPv6NetworkUtils.isIpv6InRange(ip, rangeVO.getStartIp(), rangeVO.getEndIp())) {
if (rangeVO != null && !IPv6NetworkUtils.isIpv6InRange(ip, rangeVO.getStartIp(), rangeVO.getEndIp())) {
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10134, "ip address [%s] is not in ip range [startIp %s, endIp %s]",
ip, rangeVO.getStartIp(), rangeVO.getEndIp()));
}
Expand Down
Loading