115115import com .cloud .vm .dao .UserVmDao ;
116116import com .cloud .vm .dao .VMInstanceDetailsDao ;
117117import com .google .gson .Gson ;
118+ import com .google .gson .JsonElement ;
118119import com .google .gson .JsonObject ;
120+ import com .google .gson .JsonParser ;
119121
120122import org .apache .cloudstack .api .ApiConstants ;
121123import org .apache .cloudstack .engine .orchestration .service .NetworkOrchestrationService ;
124126import org .apache .cloudstack .extension .NetworkCustomActionProvider ;
125127import org .apache .cloudstack .framework .extensions .dao .ExtensionDetailsDao ;
126128import org .apache .cloudstack .resourcedetail .dao .VpcDetailsDao ;
129+ import org .apache .commons .lang3 .EnumUtils ;
127130import org .apache .commons .lang3 .StringUtils ;
128131
129132import java .nio .charset .StandardCharsets ;
@@ -499,32 +502,14 @@ public boolean implement(Network network, NetworkOffering offering, DeployDestin
499502 implArgs .add ("--extension-ip" ); implArgs .add (safeStr (extensionIp ));
500503 implArgs .addAll (vpcArgs );
501504
502- boolean result = executeScript (network , CMD_IMPLEMENT_NETWORK , implArgs .toArray (new String [0 ]));
505+ Pair < Boolean , String > result = executeScriptAndReturnOutput (network , CMD_IMPLEMENT_NETWORK , implArgs .toArray (new String [0 ]));
503506
504- if (!result ) {
507+ if (!result . first () ) {
505508 return false ;
506509 }
507510
508- // When the extension declares vif.binding=lswitch, also update the
509- // Network row itself so listNetworks / DB queries advertise the
510- // OVN-flavoured identifier instead of the cosmetic VLAN URI the
511- // GuestNetworkGuru allocated at design-time. Format follows the
512- // legacy ovn-plugin convention: ``ovn://cs-net-<networkId>``.
513- if (isLswitchVifBinding (network )) {
514- try {
515- NetworkVO networkVo = networkDao .findById (network .getId ());
516- if (networkVo != null ) {
517- URI ovnUri = URI .create ("ovn://cs-net-" + network .getId ());
518- networkVo .setBroadcastDomainType (Networks .BroadcastDomainType .Lswitch );
519- networkVo .setBroadcastUri (ovnUri );
520- networkDao .update (networkVo .getId (), networkVo );
521- logger .debug ("implement: applied Lswitch broadcast type and ovn:// URI to network {} per extension vif.binding hint" ,
522- network .getId ());
523- }
524- } catch (Exception e ) {
525- logger .warn ("Failed to persist OVN URI on network {}: {}" , network .getId (), e .getMessage ());
526- }
527- }
511+ // Update the network properties from the output
512+ applyNetworkUpdateFromScriptOutput (network , result .second ());
528513
529514 // Step 3: Configure source NAT for both VPC and non-VPC networks for
530515 // compatibility (other network-element providers may also implement VPC tiers).
@@ -571,42 +556,8 @@ public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm
571556 return false ;
572557 }
573558
574- // VIF binding hint -- when the extension declares vif.binding=lswitch,
575- // override the NicProfile's broadcast type so OvsVifDriver picks the
576- // Lswitch path on the KVM agent. That path already emits libvirt
577- // <virtualport type='openvswitch' interfaceid='<nic.getUuid()>'/> and
578- // libvirt sets external_ids:iface-id atomically with tap creation.
579- // No agent patch is required for this binding mode.
580- if (isLswitchVifBinding (network )) {
581- // Override broadcast type + URI on the NicProfile (in-memory),
582- // and persist the same to the underlying nics row so listNics
583- // / DB queries report consistent OVN identifiers instead of
584- // the stale VLAN URI the GuestNetworkGuru allocated at
585- // design-time.
586- URI ovnUri = null ;
587- try {
588- ovnUri = URI .create ("ovn://cs-net-" + network .getId ());
589- } catch (Exception e ) {
590- logger .warn ("Failed to build OVN URI for NIC {}: {}" , nic .getId (), e .getMessage ());
591- }
592- nic .setBroadcastType (Networks .BroadcastDomainType .Lswitch );
593- if (ovnUri != null ) {
594- nic .setBroadcastUri (ovnUri );
595- nic .setIsolationUri (ovnUri );
596- try {
597- NicVO nicVo = nicDao .findById (nic .getId ());
598- if (nicVo != null ) {
599- nicVo .setBroadcastUri (ovnUri );
600- nicVo .setIsolationUri (ovnUri );
601- nicDao .update (nicVo .getId (), nicVo );
602- }
603- } catch (Exception e ) {
604- logger .warn ("Failed to persist OVN URI on nics row {}: {}" , nic .getId (), e .getMessage ());
605- }
606- }
607- logger .debug ("prepare: applied Lswitch broadcast type and ovn:// URI to NIC {} (uuid={}) on network {} per extension vif.binding hint" ,
608- nic .getId (), nic .getUuid (), network .getId ());
609- }
559+ // Sync nic with network
560+ applyNicUpdateFromNetwork (network , nic );
610561
611562 final NetworkOfferingVO offering = networkOfferingDao .findById (network .getNetworkOfferingId ());
612563 implement (network , offering , dest , context );
@@ -615,47 +566,33 @@ public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm
615566 }
616567
617568 /**
618- * Returns {@code true} when the extension that owns the given network
619- * declares {@code vif.binding=lswitch} in its {@code extension_details}.
620- * Used by {@link #prepare(Network, NicProfile, VirtualMachineProfile,
621- * DeployDestination, ReservationContext)} to switch the NIC's
622- * {@link Networks.BroadcastDomainType} to {@code Lswitch} so the KVM
623- * agent's existing {@code OvsVifDriver} Lswitch path is exercised --
624- * see the framework README for the full contract.
569+ * Returns {@code ["--nic-uuid", "<uuid>"]} when the extension so the script
570+ * can use the same UUID when needed.
625571 */
626- private boolean isLswitchVifBinding (Network network ) {
627- try {
628- Extension extension = resolveExtension (network );
629- if (extension == null ) {
630- return false ;
631- }
632- Map <String , String > details = extensionDetailsDao .listDetailsKeyPairs (extension .getId ());
633- if (details == null ) {
634- return false ;
635- }
636- String vifBinding = details .get (ExtensionHelper .VIF_BINDING_DETAIL_KEY );
637- return ExtensionHelper .VIF_BINDING_LSWITCH .equalsIgnoreCase (vifBinding );
638- } catch (Exception e ) {
639- logger .debug ("Failed to resolve vif.binding for network {}: {}" , network .getId (), e .getMessage ());
640- return false ;
572+ private List <String > getNicUuidArgs (NicProfile nic ) {
573+ if (nic == null || nic .getUuid () == null || nic .getUuid ().isBlank ()) {
574+ return Collections .emptyList ();
641575 }
576+ return List .of ("--nic-uuid" , nic .getUuid ());
642577 }
643578
644- /**
645- * Returns {@code ["--nic-uuid", "<uuid>"]} when the extension prefers the
646- * Lswitch VIF binding path so the script can use the same UUID as the LSP
647- * name (matching the {@code interfaceid} that {@code OvsVifDriver} emits).
648- * Returns an empty list when the extension does not opt in -- existing
649- * extensions that derive identifiers from the MAC keep working unchanged.
650- */
651- private List <String > getNicUuidArgs (Network network , NicProfile nic ) {
652- if (nic == null || nic .getUuid () == null || nic .getUuid ().isBlank ()) {
653- return Collections .emptyList ();
579+ private void applyNicUpdateFromNetwork (Network network , NicProfile nic ) {
580+ if (nic == null ) {
581+ return ;
654582 }
655- if (!isLswitchVifBinding (network )) {
656- return Collections .emptyList ();
583+ try {
584+ NicVO nicVo = nicDao .findById (nic .getId ());
585+ if (nicVo == null ) {
586+ return ;
587+ }
588+ if (network .getBroadcastUri () != null ) {
589+ nicVo .setBroadcastUri (network .getBroadcastUri ());
590+ nicVo .setIsolationUri (network .getBroadcastUri ());
591+ }
592+ nicDao .update (nic .getId (), nicVo );
593+ } catch (Exception e ) {
594+ logger .debug ("Failed to update nic {}: {}" , nic .getId (), e .getMessage ());
657595 }
658- return List .of ("--nic-uuid" , nic .getUuid ());
659596 }
660597
661598 @ Override
@@ -1071,6 +1008,10 @@ public boolean applyPFRules(Network network, List<PortForwardingRule> rules)
10711008 * </ul>
10721009 */
10731010 protected boolean executeScript (Network network , String command , String ... args ) {
1011+ return executeScriptAndReturnOutput (network , command , args ).first ();
1012+ }
1013+
1014+ protected Pair <Boolean , String > executeScriptAndReturnOutput (Network network , String command , String ... args ) {
10741015 Extension extension = resolveExtension (network );
10751016 File scriptFile = resolveScriptFile (network , extension );
10761017
@@ -1107,15 +1048,83 @@ protected boolean executeScript(Network network, String command, String... args)
11071048 }
11081049 if (exitCode != 0 ) {
11091050 logger .error ("Network extension script failed with exit code {}: {}" , exitCode , outputStr );
1110- return false ;
1051+ return new Pair <>( false , outputStr ) ;
11111052 }
1112- return true ;
1053+ return new Pair <>( true , outputStr ) ;
11131054 } catch (Exception e ) {
11141055 logger .error ("Failed to execute network extension script: {}" , e .getMessage (), e );
11151056 throw new CloudRuntimeException ("Failed to execute network extension script" , e );
11161057 }
11171058 }
11181059
1060+ private JsonObject parseJsonOutput (String outputStr ) {
1061+ if (StringUtils .isBlank (outputStr )) {
1062+ return null ;
1063+ }
1064+ try {
1065+ JsonElement parsed = JsonParser .parseString (outputStr );
1066+ if (!parsed .isJsonObject ()) {
1067+ logger .debug ("Ignoring non-object script output: {}" , outputStr );
1068+ return null ;
1069+ }
1070+ return parsed .getAsJsonObject ();
1071+ } catch (Exception e ) {
1072+ logger .debug ("Ignoring non-JSON script output: {}" , outputStr );
1073+ return null ;
1074+ }
1075+ }
1076+
1077+ private String getJsonString (JsonObject jsonObject , String key ) {
1078+ if (jsonObject == null || StringUtils .isBlank (key ) || !jsonObject .has (key )) {
1079+ return null ;
1080+ }
1081+ JsonElement value = jsonObject .get (key );
1082+ if (value == null || value .isJsonNull ()) {
1083+ return null ;
1084+ }
1085+ return value .getAsString ();
1086+ }
1087+
1088+ private void applyNetworkUpdateFromScriptOutput (Network network , String outputStr ) {
1089+ JsonObject outputJson = parseJsonOutput (outputStr );
1090+
1091+ String networkBroadcastUri = getJsonString (outputJson , "network.broadcast_uri" );
1092+ String networkBroadcastDomainType = getJsonString (outputJson , "network.broadcast_domain_type" );
1093+ if (networkBroadcastUri == null && networkBroadcastDomainType == null ) {
1094+ return ;
1095+ }
1096+
1097+ try {
1098+ NetworkVO networkVo = networkDao .findById (network .getId ());
1099+ if (networkVo == null ) {
1100+ return ;
1101+ }
1102+
1103+ boolean changed = false ;
1104+ if (networkBroadcastDomainType != null ) {
1105+ Networks .BroadcastDomainType domainType = EnumUtils .getEnumIgnoreCase (Networks .BroadcastDomainType .class , networkBroadcastDomainType );
1106+ if (domainType != null ) {
1107+ networkVo .setBroadcastDomainType (domainType );
1108+ changed = true ;
1109+ } else {
1110+ logger .warn ("Ignoring unknown broadcast domain type '{}' for network {}" ,
1111+ networkBroadcastDomainType , network .getId ());
1112+ }
1113+ }
1114+
1115+ if (networkBroadcastUri != null ) {
1116+ networkVo .setBroadcastUri (URI .create (networkBroadcastUri ));
1117+ changed = true ;
1118+ }
1119+
1120+ if (changed ) {
1121+ networkDao .update (networkVo .getId (), networkVo );
1122+ }
1123+ } catch (Exception e ) {
1124+ logger .warn ("Failed to update network {} from script output: {}" , network .getId (), e .getMessage ());
1125+ }
1126+ }
1127+
11191128 /**
11201129 * Writes a potentially large payload to a temporary file and passes the file path
11211130 * to the extension script via {@code payloadArgName}. This avoids argv size limits
@@ -1492,7 +1501,7 @@ public boolean addDhcpEntry(Network network, NicProfile nic, VirtualMachineProfi
14921501 args .add ("--default-nic" ); args .add (String .valueOf (nic .isDefaultNic ()));
14931502 args .add ("--domain" ); args .add (safeStr (network .getNetworkDomain ()));
14941503 args .add ("--extension-ip" ); args .add (safeStr (extensionIp ));
1495- args .addAll (getNicUuidArgs (network , nic ));
1504+ args .addAll (getNicUuidArgs (nic ));
14961505 args .addAll (getVpcIdArgs (network ));
14971506 return executeScript (network , CMD_ADD_DHCP_ENTRY , args .toArray (new String [0 ]));
14981507 }
@@ -1581,7 +1590,7 @@ public boolean removeDhcpEntry(Network network, NicProfile nic, VirtualMachinePr
15811590 args .add ("--mac" ); args .add (safeStr (nic .getMacAddress ()));
15821591 args .add ("--ip" ); args .add (safeStr (nic .getIPv4Address ()));
15831592 args .add ("--extension-ip" ); args .add (safeStr (extensionIp ));
1584- args .addAll (getNicUuidArgs (network , nic ));
1593+ args .addAll (getNicUuidArgs (nic ));
15851594 args .addAll (getVpcIdArgs (network ));
15861595 return executeScript (network , CMD_REMOVE_DHCP_ENTRY , args .toArray (new String [0 ]));
15871596 }
@@ -1604,7 +1613,7 @@ public boolean addDnsEntry(Network network, NicProfile nic, VirtualMachineProfil
16041613 args .add ("--ip" ); args .add (safeStr (nic .getIPv4Address ()));
16051614 args .add ("--hostname" ); args .add (safeStr (hostname ));
16061615 args .add ("--extension-ip" ); args .add (safeStr (extensionIp ));
1607- args .addAll (getNicUuidArgs (network , nic ));
1616+ args .addAll (getNicUuidArgs (nic ));
16081617 args .addAll (getVpcIdArgs (network ));
16091618 return executeScript (network , CMD_ADD_DNS_ENTRY , args .toArray (new String [0 ]));
16101619 }
@@ -1781,7 +1790,7 @@ public boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMa
17811790 args .add ("--ip" ); args .add (safeStr (nicIpAddress ));
17821791 args .add ("--gateway" ); args .add (safeStr (nic .getIPv4Gateway ()));
17831792 args .add ("--extension-ip" ); args .add (safeStr (ensureExtensionIp (network )));
1784- args .addAll (getNicUuidArgs (network , nic ));
1793+ args .addAll (getNicUuidArgs (nic ));
17851794 args .addAll (getVpcIdArgs (network ));
17861795 return executeScriptWithFilePayload (network , CMD_SAVE_VM_DATA , "--vm-data-file" ,
17871796 vmDataArg , args .toArray (new String [0 ]));
@@ -1805,7 +1814,7 @@ public boolean savePassword(Network network, NicProfile nic, VirtualMachineProfi
18051814 args .add ("--gateway" ); args .add (safeStr (nic .getIPv4Gateway ()));
18061815 args .add ("--password" ); args .add (password );
18071816 args .add ("--extension-ip" ); args .add (safeStr (extensionIp ));
1808- args .addAll (getNicUuidArgs (network , nic ));
1817+ args .addAll (getNicUuidArgs (nic ));
18091818 args .addAll (getVpcIdArgs (network ));
18101819 return executeScript (network , CMD_SAVE_PASSWORD , args .toArray (new String [0 ]));
18111820 }
@@ -1832,7 +1841,7 @@ public boolean saveUserData(Network network, NicProfile nic, VirtualMachineProfi
18321841 args .add ("--gateway" ); args .add (safeStr (nic .getIPv4Gateway ()));
18331842 args .add ("--userdata" ); args .add (userData );
18341843 args .add ("--extension-ip" ); args .add (safeStr (extensionIp ));
1835- args .addAll (getNicUuidArgs (network , nic ));
1844+ args .addAll (getNicUuidArgs (nic ));
18361845 args .addAll (getVpcIdArgs (network ));
18371846 return executeScript (network , CMD_SAVE_USERDATA , args .toArray (new String [0 ]));
18381847 }
@@ -1856,7 +1865,7 @@ public boolean saveSSHKey(Network network, NicProfile nic, VirtualMachineProfile
18561865 args .add ("--gateway" ); args .add (safeStr (nic .getIPv4Gateway ()));
18571866 args .add ("--sshkey" ); args .add (sshKeyBase64 );
18581867 args .add ("--extension-ip" ); args .add (safeStr (extensionIp ));
1859- args .addAll (getNicUuidArgs (network , nic ));
1868+ args .addAll (getNicUuidArgs (nic ));
18601869 args .addAll (getVpcIdArgs (network ));
18611870 return executeScript (network , CMD_SAVE_SSHKEY , args .toArray (new String [0 ]));
18621871 }
@@ -1880,7 +1889,7 @@ public boolean saveHypervisorHostname(NicProfile nic, Network network, VirtualMa
18801889 args .add ("--gateway" ); args .add (safeStr (nic .getIPv4Gateway ()));
18811890 args .add ("--hypervisor-hostname" ); args .add (hostname );
18821891 args .add ("--extension-ip" ); args .add (safeStr (extensionIp ));
1883- args .addAll (getNicUuidArgs (network , nic ));
1892+ args .addAll (getNicUuidArgs (nic ));
18841893 args .addAll (getVpcIdArgs (network ));
18851894 return executeScript (network , CMD_SAVE_HYPERVISOR_HOSTNAME , args .toArray (new String [0 ]));
18861895 }
0 commit comments