mirror of https://github.com/apache/cloudstack.git
NE: refactor the ovn support
This commit is contained in:
parent
ae23e19bb8
commit
055f9e61d8
|
|
@ -43,30 +43,6 @@ public interface ExtensionHelper {
|
|||
*/
|
||||
String NETWORK_SERVICE_CAPABILITIES_DETAIL_KEY = "network.service.capabilities";
|
||||
|
||||
/**
|
||||
* Detail key used by an OVS-backed NetworkOrchestrator extension to declare
|
||||
* how its Logical Switch Port name should be matched against the OVS
|
||||
* {@code external_ids:iface-id} written by libvirt on the hypervisor.
|
||||
*
|
||||
* <p>Currently supported value:</p>
|
||||
* <ul>
|
||||
* <li>{@code "lswitch"} — the framework sets {@code BroadcastDomainType.Lswitch}
|
||||
* on the {@link com.cloud.vm.NicProfile} during {@code prepare(...)} and
|
||||
* propagates {@code nic.getUuid()} to per-NIC script commands as
|
||||
* {@code --nic-uuid}. The extension is then expected to use that UUID as
|
||||
* the LSP name, so it matches the {@code interfaceid} that
|
||||
* {@code OvsVifDriver} emits in the libvirt {@code <virtualport>} for
|
||||
* {@code Lswitch} broadcast type.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>If absent, the framework keeps the network's broadcast type unchanged
|
||||
* (typically {@code Vlan}) and does not propagate {@code --nic-uuid}.</p>
|
||||
*/
|
||||
String VIF_BINDING_DETAIL_KEY = "vif.binding";
|
||||
|
||||
/** Value of {@link #VIF_BINDING_DETAIL_KEY} that selects the Lswitch path. */
|
||||
String VIF_BINDING_LSWITCH = "lswitch";
|
||||
|
||||
String getExtensionScriptPath(Extension extension);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -115,7 +115,9 @@ import com.cloud.vm.dao.NicDao;
|
|||
import com.cloud.vm.dao.UserVmDao;
|
||||
import com.cloud.vm.dao.VMInstanceDetailsDao;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
|
|
@ -124,6 +126,7 @@ import org.apache.cloudstack.extension.ExtensionHelper;
|
|||
import org.apache.cloudstack.extension.NetworkCustomActionProvider;
|
||||
import org.apache.cloudstack.framework.extensions.dao.ExtensionDetailsDao;
|
||||
import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao;
|
||||
import org.apache.commons.lang3.EnumUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
|
@ -499,32 +502,14 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
implArgs.add("--extension-ip"); implArgs.add(safeStr(extensionIp));
|
||||
implArgs.addAll(vpcArgs);
|
||||
|
||||
boolean result = executeScript(network, CMD_IMPLEMENT_NETWORK, implArgs.toArray(new String[0]));
|
||||
Pair<Boolean, String> result = executeScriptAndReturnOutput(network, CMD_IMPLEMENT_NETWORK, implArgs.toArray(new String[0]));
|
||||
|
||||
if (!result) {
|
||||
if (!result.first()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When the extension declares vif.binding=lswitch, also update the
|
||||
// Network row itself so listNetworks / DB queries advertise the
|
||||
// OVN-flavoured identifier instead of the cosmetic VLAN URI the
|
||||
// GuestNetworkGuru allocated at design-time. Format follows the
|
||||
// legacy ovn-plugin convention: ``ovn://cs-net-<networkId>``.
|
||||
if (isLswitchVifBinding(network)) {
|
||||
try {
|
||||
NetworkVO networkVo = networkDao.findById(network.getId());
|
||||
if (networkVo != null) {
|
||||
URI ovnUri = URI.create("ovn://cs-net-" + network.getId());
|
||||
networkVo.setBroadcastDomainType(Networks.BroadcastDomainType.Lswitch);
|
||||
networkVo.setBroadcastUri(ovnUri);
|
||||
networkDao.update(networkVo.getId(), networkVo);
|
||||
logger.debug("implement: applied Lswitch broadcast type and ovn:// URI to network {} per extension vif.binding hint",
|
||||
network.getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to persist OVN URI on network {}: {}", network.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
// Update the network properties from the output
|
||||
applyNetworkUpdateFromScriptOutput(network, result.second());
|
||||
|
||||
// Step 3: Configure source NAT for both VPC and non-VPC networks for
|
||||
// compatibility (other network-element providers may also implement VPC tiers).
|
||||
|
|
@ -571,42 +556,8 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
return false;
|
||||
}
|
||||
|
||||
// VIF binding hint -- when the extension declares vif.binding=lswitch,
|
||||
// override the NicProfile's broadcast type so OvsVifDriver picks the
|
||||
// Lswitch path on the KVM agent. That path already emits libvirt
|
||||
// <virtualport type='openvswitch' interfaceid='<nic.getUuid()>'/> and
|
||||
// libvirt sets external_ids:iface-id atomically with tap creation.
|
||||
// No agent patch is required for this binding mode.
|
||||
if (isLswitchVifBinding(network)) {
|
||||
// Override broadcast type + URI on the NicProfile (in-memory),
|
||||
// and persist the same to the underlying nics row so listNics
|
||||
// / DB queries report consistent OVN identifiers instead of
|
||||
// the stale VLAN URI the GuestNetworkGuru allocated at
|
||||
// design-time.
|
||||
URI ovnUri = null;
|
||||
try {
|
||||
ovnUri = URI.create("ovn://cs-net-" + network.getId());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to build OVN URI for NIC {}: {}", nic.getId(), e.getMessage());
|
||||
}
|
||||
nic.setBroadcastType(Networks.BroadcastDomainType.Lswitch);
|
||||
if (ovnUri != null) {
|
||||
nic.setBroadcastUri(ovnUri);
|
||||
nic.setIsolationUri(ovnUri);
|
||||
try {
|
||||
NicVO nicVo = nicDao.findById(nic.getId());
|
||||
if (nicVo != null) {
|
||||
nicVo.setBroadcastUri(ovnUri);
|
||||
nicVo.setIsolationUri(ovnUri);
|
||||
nicDao.update(nicVo.getId(), nicVo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to persist OVN URI on nics row {}: {}", nic.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
logger.debug("prepare: applied Lswitch broadcast type and ovn:// URI to NIC {} (uuid={}) on network {} per extension vif.binding hint",
|
||||
nic.getId(), nic.getUuid(), network.getId());
|
||||
}
|
||||
// Sync nic with network
|
||||
applyNicUpdateFromNetwork(network, nic);
|
||||
|
||||
final NetworkOfferingVO offering = networkOfferingDao.findById(network.getNetworkOfferingId());
|
||||
implement(network, offering, dest, context);
|
||||
|
|
@ -615,49 +566,35 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} when the extension that owns the given network
|
||||
* declares {@code vif.binding=lswitch} in its {@code extension_details}.
|
||||
* Used by {@link #prepare(Network, NicProfile, VirtualMachineProfile,
|
||||
* DeployDestination, ReservationContext)} to switch the NIC's
|
||||
* {@link Networks.BroadcastDomainType} to {@code Lswitch} so the KVM
|
||||
* agent's existing {@code OvsVifDriver} Lswitch path is exercised --
|
||||
* see the framework README for the full contract.
|
||||
* Returns {@code ["--nic-uuid", "<uuid>"]} when the extension so the script
|
||||
* can use the same UUID when needed.
|
||||
*/
|
||||
private boolean isLswitchVifBinding(Network network) {
|
||||
try {
|
||||
Extension extension = resolveExtension(network);
|
||||
if (extension == null) {
|
||||
return false;
|
||||
}
|
||||
Map<String, String> details = extensionDetailsDao.listDetailsKeyPairs(extension.getId());
|
||||
if (details == null) {
|
||||
return false;
|
||||
}
|
||||
String vifBinding = details.get(ExtensionHelper.VIF_BINDING_DETAIL_KEY);
|
||||
return ExtensionHelper.VIF_BINDING_LSWITCH.equalsIgnoreCase(vifBinding);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to resolve vif.binding for network {}: {}", network.getId(), e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code ["--nic-uuid", "<uuid>"]} when the extension prefers the
|
||||
* Lswitch VIF binding path so the script can use the same UUID as the LSP
|
||||
* name (matching the {@code interfaceid} that {@code OvsVifDriver} emits).
|
||||
* Returns an empty list when the extension does not opt in -- existing
|
||||
* extensions that derive identifiers from the MAC keep working unchanged.
|
||||
*/
|
||||
private List<String> getNicUuidArgs(Network network, NicProfile nic) {
|
||||
private List<String> getNicUuidArgs(NicProfile nic) {
|
||||
if (nic == null || nic.getUuid() == null || nic.getUuid().isBlank()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (!isLswitchVifBinding(network)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return List.of("--nic-uuid", nic.getUuid());
|
||||
}
|
||||
|
||||
private void applyNicUpdateFromNetwork(Network network, NicProfile nic) {
|
||||
if (nic == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
NicVO nicVo = nicDao.findById(nic.getId());
|
||||
if (nicVo == null) {
|
||||
return;
|
||||
}
|
||||
if (network.getBroadcastUri() != null) {
|
||||
nicVo.setBroadcastUri(network.getBroadcastUri());
|
||||
nicVo.setIsolationUri(network.getBroadcastUri());
|
||||
}
|
||||
nicDao.update(nic.getId(), nicVo);
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to update nic {}: {}", nic.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm,
|
||||
ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
|
||||
|
|
@ -1071,6 +1008,10 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
* </ul>
|
||||
*/
|
||||
protected boolean executeScript(Network network, String command, String... args) {
|
||||
return executeScriptAndReturnOutput(network, command, args).first();
|
||||
}
|
||||
|
||||
protected Pair<Boolean, String> executeScriptAndReturnOutput(Network network, String command, String... args) {
|
||||
Extension extension = resolveExtension(network);
|
||||
File scriptFile = resolveScriptFile(network, extension);
|
||||
|
||||
|
|
@ -1107,15 +1048,83 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
}
|
||||
if (exitCode != 0) {
|
||||
logger.error("Network extension script failed with exit code {}: {}", exitCode, outputStr);
|
||||
return false;
|
||||
return new Pair<>(false, outputStr);
|
||||
}
|
||||
return true;
|
||||
return new Pair<>(true, outputStr);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to execute network extension script: {}", e.getMessage(), e);
|
||||
throw new CloudRuntimeException("Failed to execute network extension script", e);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonObject parseJsonOutput(String outputStr) {
|
||||
if (StringUtils.isBlank(outputStr)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JsonElement parsed = JsonParser.parseString(outputStr);
|
||||
if (!parsed.isJsonObject()) {
|
||||
logger.debug("Ignoring non-object script output: {}", outputStr);
|
||||
return null;
|
||||
}
|
||||
return parsed.getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Ignoring non-JSON script output: {}", outputStr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getJsonString(JsonObject jsonObject, String key) {
|
||||
if (jsonObject == null || StringUtils.isBlank(key) || !jsonObject.has(key)) {
|
||||
return null;
|
||||
}
|
||||
JsonElement value = jsonObject.get(key);
|
||||
if (value == null || value.isJsonNull()) {
|
||||
return null;
|
||||
}
|
||||
return value.getAsString();
|
||||
}
|
||||
|
||||
private void applyNetworkUpdateFromScriptOutput(Network network, String outputStr) {
|
||||
JsonObject outputJson = parseJsonOutput(outputStr);
|
||||
|
||||
String networkBroadcastUri = getJsonString(outputJson, "network.broadcast_uri");
|
||||
String networkBroadcastDomainType = getJsonString(outputJson, "network.broadcast_domain_type");
|
||||
if (networkBroadcastUri == null && networkBroadcastDomainType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
NetworkVO networkVo = networkDao.findById(network.getId());
|
||||
if (networkVo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
if (networkBroadcastDomainType != null) {
|
||||
Networks.BroadcastDomainType domainType = EnumUtils.getEnumIgnoreCase(Networks.BroadcastDomainType.class, networkBroadcastDomainType);
|
||||
if (domainType != null) {
|
||||
networkVo.setBroadcastDomainType(domainType);
|
||||
changed = true;
|
||||
} else {
|
||||
logger.warn("Ignoring unknown broadcast domain type '{}' for network {}",
|
||||
networkBroadcastDomainType, network.getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (networkBroadcastUri != null) {
|
||||
networkVo.setBroadcastUri(URI.create(networkBroadcastUri));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
networkDao.update(networkVo.getId(), networkVo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to update network {} from script output: {}", network.getId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a potentially large payload to a temporary file and passes the file path
|
||||
* to the extension script via {@code payloadArgName}. This avoids argv size limits
|
||||
|
|
@ -1492,7 +1501,7 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
args.add("--default-nic"); args.add(String.valueOf(nic.isDefaultNic()));
|
||||
args.add("--domain"); args.add(safeStr(network.getNetworkDomain()));
|
||||
args.add("--extension-ip"); args.add(safeStr(extensionIp));
|
||||
args.addAll(getNicUuidArgs(network, nic));
|
||||
args.addAll(getNicUuidArgs(nic));
|
||||
args.addAll(getVpcIdArgs(network));
|
||||
return executeScript(network, CMD_ADD_DHCP_ENTRY, args.toArray(new String[0]));
|
||||
}
|
||||
|
|
@ -1581,7 +1590,7 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
args.add("--mac"); args.add(safeStr(nic.getMacAddress()));
|
||||
args.add("--ip"); args.add(safeStr(nic.getIPv4Address()));
|
||||
args.add("--extension-ip"); args.add(safeStr(extensionIp));
|
||||
args.addAll(getNicUuidArgs(network, nic));
|
||||
args.addAll(getNicUuidArgs(nic));
|
||||
args.addAll(getVpcIdArgs(network));
|
||||
return executeScript(network, CMD_REMOVE_DHCP_ENTRY, args.toArray(new String[0]));
|
||||
}
|
||||
|
|
@ -1604,7 +1613,7 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
args.add("--ip"); args.add(safeStr(nic.getIPv4Address()));
|
||||
args.add("--hostname"); args.add(safeStr(hostname));
|
||||
args.add("--extension-ip"); args.add(safeStr(extensionIp));
|
||||
args.addAll(getNicUuidArgs(network, nic));
|
||||
args.addAll(getNicUuidArgs(nic));
|
||||
args.addAll(getVpcIdArgs(network));
|
||||
return executeScript(network, CMD_ADD_DNS_ENTRY, args.toArray(new String[0]));
|
||||
}
|
||||
|
|
@ -1781,7 +1790,7 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
args.add("--ip"); args.add(safeStr(nicIpAddress));
|
||||
args.add("--gateway"); args.add(safeStr(nic.getIPv4Gateway()));
|
||||
args.add("--extension-ip"); args.add(safeStr(ensureExtensionIp(network)));
|
||||
args.addAll(getNicUuidArgs(network, nic));
|
||||
args.addAll(getNicUuidArgs(nic));
|
||||
args.addAll(getVpcIdArgs(network));
|
||||
return executeScriptWithFilePayload(network, CMD_SAVE_VM_DATA, "--vm-data-file",
|
||||
vmDataArg, args.toArray(new String[0]));
|
||||
|
|
@ -1805,7 +1814,7 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
args.add("--gateway"); args.add(safeStr(nic.getIPv4Gateway()));
|
||||
args.add("--password"); args.add(password);
|
||||
args.add("--extension-ip"); args.add(safeStr(extensionIp));
|
||||
args.addAll(getNicUuidArgs(network, nic));
|
||||
args.addAll(getNicUuidArgs(nic));
|
||||
args.addAll(getVpcIdArgs(network));
|
||||
return executeScript(network, CMD_SAVE_PASSWORD, args.toArray(new String[0]));
|
||||
}
|
||||
|
|
@ -1832,7 +1841,7 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
args.add("--gateway"); args.add(safeStr(nic.getIPv4Gateway()));
|
||||
args.add("--userdata"); args.add(userData);
|
||||
args.add("--extension-ip"); args.add(safeStr(extensionIp));
|
||||
args.addAll(getNicUuidArgs(network, nic));
|
||||
args.addAll(getNicUuidArgs(nic));
|
||||
args.addAll(getVpcIdArgs(network));
|
||||
return executeScript(network, CMD_SAVE_USERDATA, args.toArray(new String[0]));
|
||||
}
|
||||
|
|
@ -1856,7 +1865,7 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
args.add("--gateway"); args.add(safeStr(nic.getIPv4Gateway()));
|
||||
args.add("--sshkey"); args.add(sshKeyBase64);
|
||||
args.add("--extension-ip"); args.add(safeStr(extensionIp));
|
||||
args.addAll(getNicUuidArgs(network, nic));
|
||||
args.addAll(getNicUuidArgs(nic));
|
||||
args.addAll(getVpcIdArgs(network));
|
||||
return executeScript(network, CMD_SAVE_SSHKEY, args.toArray(new String[0]));
|
||||
}
|
||||
|
|
@ -1880,7 +1889,7 @@ public class NetworkExtensionElement extends AdapterBase implements
|
|||
args.add("--gateway"); args.add(safeStr(nic.getIPv4Gateway()));
|
||||
args.add("--hypervisor-hostname"); args.add(hostname);
|
||||
args.add("--extension-ip"); args.add(safeStr(extensionIp));
|
||||
args.addAll(getNicUuidArgs(network, nic));
|
||||
args.addAll(getNicUuidArgs(nic));
|
||||
args.addAll(getVpcIdArgs(network));
|
||||
return executeScript(network, CMD_SAVE_HYPERVISOR_HOSTNAME, args.toArray(new String[0]));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,9 +71,8 @@ hosts. Use it as a working example.
|
|||
8. [Capabilities Configuration](#capabilities-configuration)
|
||||
9. [VPC Networks](#vpc-networks)
|
||||
10. [Extension IP](#extension-ip)
|
||||
11. [VIF Binding for OVS-backed Extensions](#vif-binding-for-ovs-backed-extensions)
|
||||
12. [Exit Codes](#exit-codes)
|
||||
13. [Minimal Script Skeleton](#minimal-script-skeleton)
|
||||
11. [Exit Codes](#exit-codes)
|
||||
12. [Minimal Script Skeleton](#minimal-script-skeleton)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -637,7 +636,6 @@ network whose DHCP service is provided by this extension.
|
|||
| `--default-nic <bool>` | `true` if this is the VM's default NIC. |
|
||||
| `--domain <name>` | Network domain suffix (e.g. `cs.example.com`). |
|
||||
| `--extension-ip <ip>` | |
|
||||
| `--nic-uuid <uuid>` | (optional) Present only when the extension declared `vif.binding=lswitch`. Carries `nic.getUuid()` so the extension can use it as the SDN-side port identifier (matches `external_ids:iface-id` set by libvirt on the OVS tap). See [VIF Binding for OVS-backed Extensions](#vif-binding-for-ovs-backed-extensions). |
|
||||
| `--vpc-id <N>` | (optional) |
|
||||
|
||||
**`remove-dhcp-entry` arguments:**
|
||||
|
|
@ -1108,77 +1106,6 @@ To use this extension as a VPC provider:
|
|||
|
||||
---
|
||||
|
||||
## VIF Binding for OVS-backed Extensions
|
||||
|
||||
Extensions that drive OVS-based fabrics (OVN, NSX-OVS, …) need the OVS
|
||||
tap interface that libvirt creates for each VM NIC to carry the
|
||||
`external_ids:iface-id` value that the SDN controller expects. CloudStack
|
||||
already does the right thing for `BroadcastDomainType.Lswitch` networks:
|
||||
its `OvsVifDriver` emits
|
||||
|
||||
```xml
|
||||
<virtualport type='openvswitch'>
|
||||
<parameters interfaceid='<nic.getUuid()>'/>
|
||||
</virtualport>
|
||||
```
|
||||
|
||||
and libvirt sets `external_ids:iface-id=<nic-uuid>` on the tap atomically
|
||||
with port creation. No agent patch is required.
|
||||
|
||||
To opt into this binding mode an extension declares it as a top-level
|
||||
capability hint in its `extension_details`:
|
||||
|
||||
```bash
|
||||
cmk createExtension \
|
||||
name=my-ovs-sdn \
|
||||
type=NetworkOrchestrator \
|
||||
"details[0].key=network.services" \
|
||||
"details[0].value=Dhcp,Dns,UserData,SourceNat,…" \
|
||||
"details[1].key=network.service.capabilities" \
|
||||
"details[1].value=$(cat my-caps.json)" \
|
||||
"details[2].key=vif.binding" \
|
||||
"details[2].value=lswitch"
|
||||
```
|
||||
|
||||
When `vif.binding=lswitch` is present:
|
||||
|
||||
1. **`prepare()` overrides the NIC broadcast type.**
|
||||
`NetworkExtensionElement.prepare(...)` calls
|
||||
`nic.setBroadcastType(Networks.BroadcastDomainType.Lswitch)` so
|
||||
`OvsVifDriver` on the KVM agent picks the existing Lswitch path and
|
||||
emits the libvirt `<virtualport>` shown above.
|
||||
|
||||
2. **Per-NIC commands receive `--nic-uuid <uuid>`.**
|
||||
`add-dhcp-entry`, `remove-dhcp-entry`, `add-dns-entry`, `save-vm-data`,
|
||||
`save-password`, `save-userdata`, `save-sshkey`, and
|
||||
`save-hypervisor-hostname` all gain a `--nic-uuid <uuid>` argument
|
||||
carrying `nic.getUuid()`.
|
||||
|
||||
3. **The script must use `--nic-uuid` as the SDN-side port identifier.**
|
||||
Whatever object the extension creates on its controller (OVN
|
||||
Logical_Switch_Port, NSX logical port, …) **must be named exactly the
|
||||
value of `--nic-uuid`**. That is the string libvirt will write to
|
||||
`external_ids:iface-id` on the tap, so the SDN controller's local
|
||||
agent (e.g. `ovn-controller`) finds a match and binds the port.
|
||||
|
||||
When the extension does not declare `vif.binding`, the framework keeps
|
||||
the default `BroadcastDomainType.Vlan` and does not propagate
|
||||
`--nic-uuid` -- existing reference extensions (e.g.
|
||||
`network-namespace`) are unaffected.
|
||||
|
||||
### Why not the extension setting `iface-id` remotely?
|
||||
|
||||
The OVS tap only exists *after* libvirt creates the VM, so any remote
|
||||
write from the extension would race `ovn-controller` on the host. By
|
||||
letting libvirt do the write atomically with tap creation, the binding
|
||||
is ready by the time the controller scans the bridge.
|
||||
|
||||
The extension may still talk OVSDB to the host (read-only checks,
|
||||
`bridge-mappings` setup, post-incident repair) -- but never for the
|
||||
boot path.
|
||||
|
||||
---
|
||||
|
||||
## Exit Codes
|
||||
|
||||
| Exit code | Meaning |
|
||||
|
|
|
|||
Loading…
Reference in New Issue