Skip to content

Commit 046d023

Browse files
committed
<fix>[compute]: fix user define param error
when user define ip/netmask/gateway, then gateway must be in the cidr of ip/netamsl Resolves: ZSTAC-83321 Change-Id: I78616376707476666a7a72786a7062766272686b
1 parent 69ae62a commit 046d023

6 files changed

Lines changed: 679 additions & 375 deletions

File tree

compute/src/main/java/org/zstack/compute/vm/StaticIpOperator.java

Lines changed: 287 additions & 62 deletions
Large diffs are not rendered by default.

compute/src/main/java/org/zstack/compute/vm/VmInstanceApiInterceptor.java

Lines changed: 46 additions & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.google.gson.JsonSyntaxException;
44
import org.apache.commons.collections.CollectionUtils;
55
import org.apache.commons.lang.StringUtils;
6-
import org.apache.commons.net.util.SubnetUtils;
76
import org.springframework.beans.factory.annotation.Autowired;
87
import org.springframework.transaction.annotation.Transactional;
98
import org.zstack.core.Platform;
@@ -58,9 +57,7 @@
5857

5958
import static org.zstack.core.Platform.argerr;
6059
import static org.zstack.core.Platform.operr;
61-
import static org.zstack.utils.CollectionDSL.e;
6260
import static org.zstack.utils.CollectionDSL.list;
63-
import static org.zstack.utils.CollectionDSL.map;
6461
import static org.zstack.utils.CollectionUtils.getDuplicateElementsOfList;
6562
import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*;
6663

@@ -115,161 +112,6 @@ private void validateStaticIpCommon(VmNicVO vmNicVO, L3NetworkVO l3NetworkVO, St
115112
}
116113
}
117114

118-
/**
119-
* Determine whether to use the NIC's existing IPv4 parameters (netmask/gateway).
120-
* Condition: existingIp is non-null with non-empty netmask and non-empty gateway,
121-
* and the IP falls within the CIDR formed by existingIp's gateway + netmask.
122-
*/
123-
private boolean shouldUseExistingIpv4(String ip, UsedIpVO existingIp) {
124-
if (existingIp == null || StringUtils.isEmpty(existingIp.getNetmask())) {
125-
return false;
126-
}
127-
if (StringUtils.isEmpty(existingIp.getGateway())) {
128-
return false;
129-
}
130-
try {
131-
SubnetUtils.SubnetInfo info = NetworkUtils.getSubnetInfo(
132-
new SubnetUtils(existingIp.getGateway(), existingIp.getNetmask()));
133-
return NetworkUtils.isIpv4InRange(ip, info.getLowAddress(), info.getHighAddress());
134-
} catch (Exception e) {
135-
return false;
136-
}
137-
}
138-
139-
/**
140-
* Determine whether to use the NIC's existing IPv6 parameters (prefix/gateway).
141-
* Condition: existingIp is non-null with non-null prefixLen and non-empty gateway,
142-
* and the IP falls within the CIDR formed by existingIp's gateway + prefixLen.
143-
*/
144-
private boolean shouldUseExistingIpv6(String ip6, UsedIpVO existingIp) {
145-
if (existingIp == null || existingIp.getPrefixLen() == null) {
146-
return false;
147-
}
148-
if (StringUtils.isEmpty(existingIp.getGateway())) {
149-
return false;
150-
}
151-
try {
152-
return IPv6NetworkUtils.isIpv6InCidrRange(ip6,
153-
existingIp.getGateway() + "/" + existingIp.getPrefixLen());
154-
} catch (Exception e) {
155-
return false;
156-
}
157-
}
158-
159-
/**
160-
* Resolve IPv4 netmask and gateway based on 4 cases:
161-
* (a) Both netmask+gateway provided: use user input as-is
162-
* (b) Gateway provided, no netmask: if ip and gateway both in L3 CIDR, use CIDR netmask; else error
163-
* (c) Netmask provided, no gateway: if netmask == CIDR netmask, use CIDR gateway; else if default/sole NIC, error; else gateway=""
164-
* (d) Neither provided: if existingIp usable (APISetVmStaticIpMsg), use it; else if in L3 CIDR, use CIDR; else error
165-
*
166-
* @param existingIp pass null for APIChangeVmNicNetworkMsg (no existing IP on dest L3)
167-
*/
168-
private String[] resolveIpv4NetmaskAndGateway(String ip, String userNetmask, String userGateway,
169-
List<NormalIpRangeVO> ipv4Ranges, String l3Uuid, String defaultL3Uuid, int vmNicCount, UsedIpVO existingIp) {
170-
boolean hasNetmask = StringUtils.isNotEmpty(userNetmask);
171-
boolean hasGateway = StringUtils.isNotEmpty(userGateway);
172-
173-
// case (a): both provided
174-
if (hasNetmask && hasGateway) {
175-
return new String[]{userNetmask, userGateway};
176-
}
177-
178-
NormalIpRangeVO matchedRange = IpRangeHelper.findIpRangeByCidr(ip, ipv4Ranges);
179-
180-
// case (b): gateway provided, no netmask
181-
if (hasGateway) {
182-
if (matchedRange != null && matchedRange.getNetworkCidr() != null
183-
&& NetworkUtils.isIpv4InCidr(userGateway, matchedRange.getNetworkCidr())) {
184-
return new String[]{matchedRange.getNetmask(), userGateway};
185-
}
186-
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10323,
187-
"gateway[%s] is provided but IP[%s] and gateway are not both in L3 network CIDR, netmask must be specified",
188-
userGateway, ip));
189-
}
190-
191-
// case (c): netmask provided, no gateway
192-
if (hasNetmask) {
193-
if (matchedRange != null && userNetmask.equals(matchedRange.getNetmask())) {
194-
return new String[]{matchedRange.getNetmask(), matchedRange.getGateway()};
195-
}
196-
if (l3Uuid.equals(defaultL3Uuid) || vmNicCount == 1) {
197-
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10324,
198-
"netmask[%s] does not match L3 CIDR netmask and the NIC is the default or sole network, gateway must be specified",
199-
userNetmask));
200-
}
201-
return new String[]{userNetmask, ""};
202-
}
203-
204-
// case (d): neither provided
205-
if (existingIp != null && shouldUseExistingIpv4(ip, existingIp)) {
206-
return new String[]{existingIp.getNetmask(), existingIp.getGateway()};
207-
}
208-
if (matchedRange != null) {
209-
return new String[]{matchedRange.getNetmask(), matchedRange.getGateway()};
210-
}
211-
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10325,
212-
"IP[%s] is outside all L3 network CIDRs and no existing IP parameters available, netmask and gateway must be specified",
213-
ip));
214-
}
215-
216-
/**
217-
* Resolve IPv6 prefix and gateway based on 4 cases (mirrors IPv4 logic):
218-
* (a) Both prefix+gateway provided: use user input as-is
219-
* (b) Gateway provided, no prefix: if ip and gateway both in L3 CIDR, use CIDR prefix; else error
220-
* (c) Prefix provided, no gateway: if prefix == CIDR prefix, use CIDR gateway; else if default/sole NIC, error; else gateway=""
221-
* (d) Neither provided: if existingIp usable (APISetVmStaticIpMsg), use it; else if in L3 CIDR, use CIDR; else error
222-
*
223-
* @param existingIp pass null for APIChangeVmNicNetworkMsg (no existing IP on dest L3)
224-
*/
225-
private String[] resolveIpv6PrefixAndGateway(String ip6, String userPrefix, String userGateway,
226-
List<NormalIpRangeVO> ipv6Ranges, String l3Uuid, String defaultL3Uuid, int vmNicCount, UsedIpVO existingIp) {
227-
boolean hasPrefix = StringUtils.isNotEmpty(userPrefix);
228-
boolean hasGateway = StringUtils.isNotEmpty(userGateway);
229-
230-
// case (a): both provided
231-
if (hasPrefix && hasGateway) {
232-
return new String[]{userPrefix, userGateway};
233-
}
234-
235-
NormalIpRangeVO matchedRange = IpRangeHelper.findIpRangeByCidr(ip6, ipv6Ranges);
236-
237-
// case (b): gateway provided, no prefix
238-
if (hasGateway) {
239-
if (matchedRange != null && matchedRange.getNetworkCidr() != null
240-
&& IPv6NetworkUtils.isIpv6InCidrRange(userGateway, matchedRange.getNetworkCidr())) {
241-
return new String[]{matchedRange.getPrefixLen().toString(), userGateway};
242-
}
243-
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10326,
244-
"gateway[%s] is provided but IPv6[%s] and gateway are not both in L3 network CIDR, prefix must be specified",
245-
userGateway, ip6));
246-
}
247-
248-
// case (c): prefix provided, no gateway
249-
if (hasPrefix) {
250-
if (matchedRange != null && userPrefix.equals(matchedRange.getPrefixLen().toString())) {
251-
return new String[]{matchedRange.getPrefixLen().toString(), matchedRange.getGateway()};
252-
}
253-
if (l3Uuid.equals(defaultL3Uuid) || vmNicCount == 1) {
254-
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10327,
255-
"prefix[%s] does not match L3 CIDR prefix and the NIC is the default or sole network, gateway must be specified",
256-
userPrefix));
257-
}
258-
return new String[]{userPrefix, ""};
259-
}
260-
261-
// case (d): neither provided
262-
if (existingIp != null && shouldUseExistingIpv6(ip6, existingIp)) {
263-
return new String[]{existingIp.getPrefixLen().toString(), existingIp.getGateway()};
264-
}
265-
if (matchedRange != null) {
266-
return new String[]{matchedRange.getPrefixLen().toString(), matchedRange.getGateway()};
267-
}
268-
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10328,
269-
"IPv6[%s] is outside all L3 network CIDRs and no existing IP parameters available, prefix and gateway must be specified",
270-
ip6));
271-
}
272-
273115
/**
274116
* Check whether an IP is already in use (using error code ORG_ZSTACK_COMPUTE_VM_10105).
275117
*/
@@ -504,60 +346,34 @@ private void validate(APIChangeVmNicNetworkMsg msg) {
504346
}
505347
}
506348

507-
new StaticIpOperator().validateSystemTagInApiMessage(msg);
349+
// Build NicRoleContext for resolve logic
350+
String defaultL3Uuid = Q.New(VmInstanceVO.class)
351+
.select(VmInstanceVO_.defaultL3NetworkUuid)
352+
.eq(VmInstanceVO_.uuid, vmUuid)
353+
.findValue();
354+
int vmNicCount = Q.New(VmNicVO.class).eq(VmNicVO_.vmInstanceUuid, vmUuid).count().intValue();
355+
boolean isDefaultNic = srcL3Uuid.equals(defaultL3Uuid);
356+
boolean isOnlyNic = vmNicCount == 1;
508357

509-
// Resolve netmask/gateway for static IPs in systemTags, overriding what validateSystemTagInApiMessage may have set
510-
{
511-
String destL3Uuid = msg.getDestL3NetworkUuid();
512-
Map<String, NicIpAddressInfo> nicNetworkInfo = new StaticIpOperator().getNicNetworkInfoBySystemTag(msg.getSystemTags());
513-
NicIpAddressInfo nicIpInfo = nicNetworkInfo.get(destL3Uuid);
514-
if (nicIpInfo != null) {
515-
List<NormalIpRangeVO> destIpv4Ranges = Q.New(NormalIpRangeVO.class)
516-
.eq(NormalIpRangeVO_.l3NetworkUuid, destL3Uuid)
517-
.eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv4).list();
518-
List<NormalIpRangeVO> destIpv6Ranges = Q.New(NormalIpRangeVO.class)
519-
.eq(NormalIpRangeVO_.l3NetworkUuid, destL3Uuid)
520-
.eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv6).list();
521-
String defaultL3Uuid = Q.New(VmInstanceVO.class)
522-
.select(VmInstanceVO_.defaultL3NetworkUuid)
523-
.eq(VmInstanceVO_.uuid, vmUuid)
524-
.findValue();
525-
int vmNicCount = Q.New(VmNicVO.class).eq(VmNicVO_.vmInstanceUuid, vmUuid).count().intValue();
526-
527-
// Remove existing netmask/gateway/prefix/ipv6Gateway tags for dest L3 from systemTags
528-
if (msg.getSystemTags() != null) {
529-
msg.getSystemTags().removeIf(tag ->
530-
VmSystemTags.IPV4_NETMASK.isMatch(tag) || VmSystemTags.IPV4_GATEWAY.isMatch(tag)
531-
|| VmSystemTags.IPV6_PREFIX.isMatch(tag) || VmSystemTags.IPV6_GATEWAY.isMatch(tag));
532-
}
358+
StaticIpOperator staticIpOp = new StaticIpOperator();
359+
Map<String, NicIpAddressInfo> nicNetworkInfo = staticIpOp.getNicNetworkInfoBySystemTag(msg.getSystemTags());
533360

534-
// Resolve and add IPv4 netmask/gateway
535-
if (StringUtils.isNotEmpty(nicIpInfo.ipv4Address)) {
536-
String[] ipv4Result = resolveIpv4NetmaskAndGateway(nicIpInfo.ipv4Address,
537-
nicIpInfo.ipv4Netmask, nicIpInfo.ipv4Gateway,
538-
destIpv4Ranges, destL3Uuid, defaultL3Uuid, vmNicCount, null);
539-
msg.getSystemTags().add(VmSystemTags.IPV4_NETMASK.instantiateTag(
540-
map(e(VmSystemTags.IPV4_NETMASK_L3_UUID_TOKEN, destL3Uuid),
541-
e(VmSystemTags.IPV4_NETMASK_TOKEN, ipv4Result[0]))));
542-
msg.getSystemTags().add(VmSystemTags.IPV4_GATEWAY.instantiateTag(
543-
map(e(VmSystemTags.IPV4_GATEWAY_L3_UUID_TOKEN, destL3Uuid),
544-
e(VmSystemTags.IPV4_GATEWAY_TOKEN, ipv4Result[1]))));
545-
}
361+
// Validate IP availability
362+
staticIpOp.validateIpAvailability(nicNetworkInfo);
546363

547-
// Resolve and add IPv6 prefix/gateway
548-
if (StringUtils.isNotEmpty(nicIpInfo.ipv6Address)) {
549-
String[] ipv6Result = resolveIpv6PrefixAndGateway(nicIpInfo.ipv6Address,
550-
nicIpInfo.ipv6Prefix, nicIpInfo.ipv6Gateway,
551-
destIpv6Ranges, destL3Uuid, defaultL3Uuid, vmNicCount, null);
552-
msg.getSystemTags().add(VmSystemTags.IPV6_PREFIX.instantiateTag(
553-
map(e(VmSystemTags.IPV6_PREFIX_L3_UUID_TOKEN, destL3Uuid),
554-
e(VmSystemTags.IPV6_PREFIX_TOKEN, ipv6Result[0]))));
555-
msg.getSystemTags().add(VmSystemTags.IPV6_GATEWAY.instantiateTag(
556-
map(e(VmSystemTags.IPV6_GATEWAY_L3_UUID_TOKEN, destL3Uuid),
557-
e(VmSystemTags.IPV6_GATEWAY_TOKEN,
558-
IPv6NetworkUtils.ipv6AddressToTagValue(ipv6Result[1])))));
559-
}
560-
}
364+
// Resolve netmask/gateway using unified logic (no existingIp reuse for ChangeNicNetwork)
365+
StaticIpOperator.NicRoleContext nicRole = new StaticIpOperator.NicRoleContext(isDefaultNic, isOnlyNic);
366+
List<String> resolvedTags = staticIpOp.fillUpStaticIpInfoToVmNics(nicNetworkInfo,
367+
nicRole, new StaticIpOperator.ExistingIpContext());
368+
369+
// Remove any existing netmask/gateway/prefix tags, then add resolved ones
370+
if (msg.getSystemTags() != null) {
371+
msg.getSystemTags().removeIf(tag ->
372+
VmSystemTags.IPV4_NETMASK.isMatch(tag) || VmSystemTags.IPV4_GATEWAY.isMatch(tag)
373+
|| VmSystemTags.IPV6_PREFIX.isMatch(tag) || VmSystemTags.IPV6_GATEWAY.isMatch(tag));
374+
}
375+
if (!resolvedTags.isEmpty()) {
376+
msg.getSystemTags().addAll(resolvedTags);
561377
}
562378

563379
Map<String, List<String>> staticIps = new StaticIpOperator().getStaticIpbySystemTag(msg.getSystemTags());
@@ -749,6 +565,7 @@ protected void scripts() {
749565
throw new ApiMessageInterceptionException(argerr(
750566
ORG_ZSTACK_COMPUTE_VM_10124, "the VM cannot do cpu hot plug because of disabling cpu hot plug. Please stop the VM then do the cpu hot plug again"
751567
));
568+
752569
}
753570

754571
if (memorySize != null && memorySize != vo.getMemorySize()) {
@@ -811,12 +628,6 @@ private void validate(APISetVmStaticIpMsg msg) {
811628
throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_VM_10135, "could not set ip address, due to no ip address is specified"));
812629
}
813630
}
814-
List<NormalIpRangeVO> ipv4Ranges = Q.New(NormalIpRangeVO.class)
815-
.eq(NormalIpRangeVO_.l3NetworkUuid, msg.getL3NetworkUuid())
816-
.eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv4).list();
817-
List<NormalIpRangeVO> ipv6Ranges = Q.New(NormalIpRangeVO.class)
818-
.eq(NormalIpRangeVO_.l3NetworkUuid, msg.getL3NetworkUuid())
819-
.eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv6).list();
820631
List<VmNicVO> vmNics = Q.New(VmNicVO.class).eq(VmNicVO_.vmInstanceUuid, msg.getVmInstanceUuid()).list();
821632
boolean l3Found = false;
822633

@@ -874,18 +685,32 @@ private void validate(APISetVmStaticIpMsg msg) {
874685
.select(VmInstanceVO_.defaultL3NetworkUuid)
875686
.eq(VmInstanceVO_.uuid, msg.getVmInstanceUuid())
876687
.findValue();
688+
boolean isDefaultNic = msg.getL3NetworkUuid().equals(defaultL3NetworkUuid);
689+
boolean isOnlyNic = vmNics.size() == 1;
690+
691+
StaticIpOperator staticIpOp = new StaticIpOperator();
692+
StaticIpOperator.NicRoleContext nicRole = new StaticIpOperator.NicRoleContext(isDefaultNic, isOnlyNic);
693+
StaticIpOperator.ExistingIpContext existingIpCtx = new StaticIpOperator.ExistingIpContext();
694+
existingIpCtx.putIpv4(msg.getL3NetworkUuid(), existingIpv4);
695+
existingIpCtx.putIpv6(msg.getL3NetworkUuid(), existingIpv6);
877696

878-
// Fill parameters and check IP occupation
697+
// Fill parameters and check IP occupation using unified resolve
879698
if (normalizedIp != null) {
880-
String[] ipv4Result = resolveIpv4NetmaskAndGateway(normalizedIp, msg.getNetmask(), msg.getGateway(),
881-
ipv4Ranges, msg.getL3NetworkUuid(), defaultL3NetworkUuid, vmNics.size(), existingIpv4);
699+
List<NormalIpRangeVO> ipv4Ranges = Q.New(NormalIpRangeVO.class)
700+
.eq(NormalIpRangeVO_.l3NetworkUuid, msg.getL3NetworkUuid())
701+
.eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv4).list();
702+
String[] ipv4Result = staticIpOp.resolveIpv4NetmaskAndGateway(normalizedIp, msg.getNetmask(), msg.getGateway(),
703+
ipv4Ranges, nicRole, existingIpv4);
882704
msg.setNetmask(ipv4Result[0]);
883705
msg.setGateway(ipv4Result[1]);
884706
checkIpOccupied(normalizedIp, msg.getL3NetworkUuid());
885707
}
886708
if (normalizedIp6 != null) {
887-
String[] ipv6Result = resolveIpv6PrefixAndGateway(normalizedIp6, msg.getIpv6Prefix(), msg.getIpv6Gateway(),
888-
ipv6Ranges, msg.getL3NetworkUuid(), defaultL3NetworkUuid, vmNics.size(), existingIpv6);
709+
List<NormalIpRangeVO> ipv6Ranges = Q.New(NormalIpRangeVO.class)
710+
.eq(NormalIpRangeVO_.l3NetworkUuid, msg.getL3NetworkUuid())
711+
.eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv6).list();
712+
String[] ipv6Result = staticIpOp.resolveIpv6PrefixAndGateway(normalizedIp6, msg.getIpv6Prefix(), msg.getIpv6Gateway(),
713+
ipv6Ranges, nicRole, existingIpv6);
889714
msg.setIpv6Prefix(ipv6Result[0]);
890715
msg.setIpv6Gateway(ipv6Result[1]);
891716
checkIpOccupied(normalizedIp6, msg.getL3NetworkUuid());
@@ -1711,4 +1536,4 @@ private void validate(APIFstrimVmMsg msg) {
17111536
}
17121537
msg.setHostUuid(t.get(1, String.class));
17131538
}
1714-
}
1539+
}

0 commit comments

Comments
 (0)