diff --git a/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java index 49404068051c..96eb0a22d4fd 100644 --- a/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/ExternalGuestNetworkGuru.java @@ -93,18 +93,50 @@ public ExternalGuestNetworkGuru() { _isolationMethods = new IsolationMethod[] {new IsolationMethod("GRE"), new IsolationMethod("L3"), new IsolationMethod("VLAN")}; } + /** + * Network providers that ship their own {@link com.cloud.network.guru.NetworkGuru}. When an + * offering binds any of its services to one of these providers we must let that provider's + * guru claim the network exclusively. Otherwise this generic guru also matches (because the + * physical network usually still carries a VLAN/GRE/L3 isolation method as a fallback) and + * persists a duplicate, never-implemented row alongside the real one — breaking idempotency + * and confusing both the UI and the cleanup paths. + * + *

Looked up by name (rather than referencing the {@link Provider} constants directly) so + * this list keeps compiling against branches where one of the optional providers is not yet + * present in the API module — e.g. {@code Provider.Ovn} only landed in 4.23.

+ */ + private static final java.util.Set SPECIALIZED_GUEST_GURU_PROVIDER_NAMES = + java.util.Collections.unmodifiableSet(new java.util.HashSet<>(java.util.Arrays.asList( + "Ovn", "Netris", "Nsx", "Tungsten", "BigSwitchBcf", + "NiciraNvp", "Opendaylight", "BrocadeVcs", "Ovs"))); + @Override protected boolean canHandle(NetworkOffering offering, final NetworkType networkType, final PhysicalNetwork physicalNetwork) { // This guru handles only Guest Isolated network that supports Source // nat service - if (networkType == NetworkType.Advanced && isMyTrafficType(offering.getTrafficType()) + if (!(networkType == NetworkType.Advanced && isMyTrafficType(offering.getTrafficType()) && (offering.getGuestType() == Network.GuestType.Isolated || offering.getGuestType() == GuestType.L2) - && isMyIsolationMethod(physicalNetwork) && !offering.isSystemOnly()) { - return true; - } else { + && isMyIsolationMethod(physicalNetwork) && !offering.isSystemOnly())) { logger.trace("We only take care of Guest networks of type " + GuestType.Isolated + " in zone of type " + NetworkType.Advanced); return false; } + // If any service in the offering is bound to a provider that owns its own NetworkGuru, + // bail. Otherwise both this guru and the specialized one would match the same offering + // (the physical network carries VLAN as a fallback isolation method alongside OVN/etc.), + // and setupNetwork would persist two NetworkVO rows — the specialized guru implements + // its row, this guru's row stays Allocated forever and clutters listNetworks. + java.util.List offeringProviders = networkOfferingServiceMapDao.getDistinctProviders(offering.getId()); + if (offeringProviders != null) { + for (String providerName : offeringProviders) { + if (providerName != null && SPECIALIZED_GUEST_GURU_PROVIDER_NAMES.contains(providerName)) { + logger.debug("Offering {} declares provider {} which owns a dedicated NetworkGuru; " + + "ExternalGuestNetworkGuru declines to handle.", + offering.getId(), providerName); + return false; + } + } + } + return true; } @Override