Skip to content

Commit 055f9e6

Browse files
committed
NE: refactor the ovn support
1 parent ae23e19 commit 055f9e6

3 files changed

Lines changed: 114 additions & 202 deletions

File tree

api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,6 @@ public interface ExtensionHelper {
4343
*/
4444
String NETWORK_SERVICE_CAPABILITIES_DETAIL_KEY = "network.service.capabilities";
4545

46-
/**
47-
* Detail key used by an OVS-backed NetworkOrchestrator extension to declare
48-
* how its Logical Switch Port name should be matched against the OVS
49-
* {@code external_ids:iface-id} written by libvirt on the hypervisor.
50-
*
51-
* <p>Currently supported value:</p>
52-
* <ul>
53-
* <li>{@code "lswitch"} — the framework sets {@code BroadcastDomainType.Lswitch}
54-
* on the {@link com.cloud.vm.NicProfile} during {@code prepare(...)} and
55-
* propagates {@code nic.getUuid()} to per-NIC script commands as
56-
* {@code --nic-uuid}. The extension is then expected to use that UUID as
57-
* the LSP name, so it matches the {@code interfaceid} that
58-
* {@code OvsVifDriver} emits in the libvirt {@code <virtualport>} for
59-
* {@code Lswitch} broadcast type.</li>
60-
* </ul>
61-
*
62-
* <p>If absent, the framework keeps the network's broadcast type unchanged
63-
* (typically {@code Vlan}) and does not propagate {@code --nic-uuid}.</p>
64-
*/
65-
String VIF_BINDING_DETAIL_KEY = "vif.binding";
66-
67-
/** Value of {@link #VIF_BINDING_DETAIL_KEY} that selects the Lswitch path. */
68-
String VIF_BINDING_LSWITCH = "lswitch";
69-
7046
String getExtensionScriptPath(Extension extension);
7147

7248
/**

framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/network/NetworkExtensionElement.java

Lines changed: 112 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@
115115
import com.cloud.vm.dao.UserVmDao;
116116
import com.cloud.vm.dao.VMInstanceDetailsDao;
117117
import com.google.gson.Gson;
118+
import com.google.gson.JsonElement;
118119
import com.google.gson.JsonObject;
120+
import com.google.gson.JsonParser;
119121

120122
import org.apache.cloudstack.api.ApiConstants;
121123
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@@ -124,6 +126,7 @@
124126
import org.apache.cloudstack.extension.NetworkCustomActionProvider;
125127
import org.apache.cloudstack.framework.extensions.dao.ExtensionDetailsDao;
126128
import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao;
129+
import org.apache.commons.lang3.EnumUtils;
127130
import org.apache.commons.lang3.StringUtils;
128131

129132
import 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

Comments
 (0)