33import com .google .gson .JsonSyntaxException ;
44import org .apache .commons .collections .CollectionUtils ;
55import org .apache .commons .lang .StringUtils ;
6- import org .apache .commons .net .util .SubnetUtils ;
76import org .springframework .beans .factory .annotation .Autowired ;
87import org .springframework .transaction .annotation .Transactional ;
98import org .zstack .core .Platform ;
5857
5958import static org .zstack .core .Platform .argerr ;
6059import static org .zstack .core .Platform .operr ;
61- import static org .zstack .utils .CollectionDSL .e ;
6260import static org .zstack .utils .CollectionDSL .list ;
63- import static org .zstack .utils .CollectionDSL .map ;
6461import static org .zstack .utils .CollectionUtils .getDuplicateElementsOfList ;
6562import 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