Copilot commented on code in PR #13032:
URL: https://github.com/apache/cloudstack/pull/13032#discussion_r3100743358


##########
framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java:
##########
@@ -370,16 +443,55 @@ protected Extension 
getExtensionFromResource(ExtensionCustomAction.ResourceType
             VirtualMachine vm = (VirtualMachine) object;
             Pair<Long, Long> clusterHostId = 
virtualMachineManager.findClusterAndHostIdForVm(vm, false);
             clusterId = clusterHostId.first();
-        }
-        if (clusterId == null) {
-            return null;
-        }
-        ExtensionResourceMapVO mapVO =
-                extensionResourceMapDao.findByResourceIdAndType(clusterId, 
ExtensionResourceMap.ResourceType.Cluster);
-        if (mapVO == null) {
+            if (clusterId == null) {
+                return null;
+            }
+            ExtensionResourceMapVO mapVO =
+                    extensionResourceMapDao.findByResourceIdAndType(clusterId, 
ExtensionResourceMap.ResourceType.Cluster);
+            if (mapVO == null) {
+                return null;
+            }
+            return extensionDao.findById(mapVO.getExtensionId());
+        } else if (resourceType == ExtensionCustomAction.ResourceType.Network) 
{
+            Network network = (Network) object;
+            Long physicalNetworkId = network.getPhysicalNetworkId();
+            if (physicalNetworkId == null) {
+                return null;
+            }
+            // Use provider-based lookup: match the network's service-map 
providers
+            // against extension names registered on the physical network.
+            // This correctly handles multiple different extensions on the 
same physical network.
+            List<String> providers = 
networkServiceMapDao.getDistinctProviders(network.getId());
+            if (CollectionUtils.isNotEmpty(providers)) {
+                for (String providerName : providers) {
+                    Extension ext = 
getExtensionForPhysicalNetworkAndProvider(physicalNetworkId, providerName);
+                    if (ext != null) {
+                        return ext;
+                    }
+                }
+            }
+            // Fallback: return the first extension registered on the physical 
network
+            List<ExtensionResourceMapVO> maps = 
extensionResourceMapDao.listByResourceIdAndType(
+                    physicalNetworkId, 
ExtensionResourceMap.ResourceType.PhysicalNetwork);
+            if (CollectionUtils.isEmpty(maps)) {
+                return null;
+            }
+            return extensionDao.findById(maps.get(0).getExtensionId());

Review Comment:
   `getExtensionFromResource` falls back to returning the *first* extension 
registered on the physical network when none of the network's service-map 
providers match an extension name. This can route custom actions to the wrong 
extension (and is nondeterministic if multiple extensions are registered), and 
it can also incorrectly return an extension for networks that aren't actually 
managed by any extension provider. Consider removing this fallback, or only 
using it when there's exactly one registered extension and the network is 
confirmed to use an extension-backed provider.
   ```suggestion
               return null;
   ```



##########
server/src/main/java/com/cloud/network/NetworkServiceImpl.java:
##########
@@ -400,6 +401,8 @@ public class NetworkServiceImpl extends ManagerBase 
implements NetworkService, C
     @Inject
     NetworkDetailsDao _networkDetailsDao;
     @Inject
+    ExtensionsManager extensionsManager;
+    @Inject

Review Comment:
   `ExtensionsManager extensionsManager` is injected but not referenced 
anywhere in this class. If it isn't needed, please remove the injection/import 
to avoid confusion; if it is intended for upcoming logic, consider adding the 
usage in this PR or a TODO explaining why it's required.



##########
framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java:
##########
@@ -934,6 +1173,246 @@ public ExtensionResourceMap 
registerExtensionWithCluster(Cluster cluster, Extens
         return result;
     }
 
+    protected ExtensionResourceMap 
registerExtensionWithPhysicalNetwork(PhysicalNetworkVO physicalNetwork,
+                  Extension extension, Map<String, String> details) {
+        // Only NetworkOrchestrator extensions can be registered with physical 
networks
+        if (!Extension.Type.NetworkOrchestrator.equals(extension.getType())) {
+            throw new InvalidParameterValueException(String.format(
+                    "Only extensions of type %s can be registered with a 
physical network. "
+                    + "Extension '%s' is of type %s.",
+                    Extension.Type.NetworkOrchestrator.name(),
+                    extension.getName(), extension.getType().name()));
+        }
+
+        // Block registering the exact same extension twice on the same 
physical network
+        final ExtensionResourceMap.ResourceType resourceType = 
ExtensionResourceMap.ResourceType.PhysicalNetwork;
+        List<ExtensionResourceMapVO> existing = 
extensionResourceMapDao.listByResourceIdAndType(
+                physicalNetwork.getId(), resourceType);
+        if (existing != null) {
+            for (ExtensionResourceMapVO ex : existing) {
+                if (ex.getExtensionId() == extension.getId()) {
+                    throw new CloudRuntimeException(String.format(
+                            "Extension '%s' is already registered with 
physical network %s",
+                            extension.getName(), physicalNetwork.getId()));
+                }
+            }
+        }
+
+        // Resolve which services this extension provides from its 
network.services detail
+        Set<String> services = resolveExtensionServices(extension);
+
+        return 
Transaction.execute((TransactionCallbackWithException<ExtensionResourceMap, 
CloudRuntimeException>) status -> {
+            // 1. Persist the extension<->physical-network mapping
+            ExtensionResourceMapVO extensionMap = new 
ExtensionResourceMapVO(extension.getId(),
+                    physicalNetwork.getId(), resourceType);
+            ExtensionResourceMapVO savedExtensionMap = 
extensionResourceMapDao.persist(extensionMap);
+
+            // 2. Persist device credentials / details
+            List<ExtensionResourceMapDetailsVO> detailsVOList = new 
ArrayList<>();
+            if (MapUtils.isNotEmpty(details)) {
+                for (Map.Entry<String, String> entry : details.entrySet()) {
+                    boolean display = 
!SENSITIVE_DETAIL_KEYS.contains(entry.getKey().toLowerCase());
+                    detailsVOList.add(new 
ExtensionResourceMapDetailsVO(savedExtensionMap.getId(),
+                            entry.getKey(), entry.getValue(), display));
+                }
+                extensionResourceMapDetailsDao.saveDetails(detailsVOList);
+            }
+
+            // 3. Auto-create the NetworkServiceProvider entry for this 
extension so that
+            //    the services are visible in the UI and in 
listSupportedNetworkServices.
+            //    The NSP name equals the extension name; state is Enabled by 
default.
+            PhysicalNetworkServiceProviderVO existingNsp =
+                    physicalNetworkServiceProviderDao.findByServiceProvider(
+                            physicalNetwork.getId(), extension.getName());
+            if (existingNsp == null) {
+                PhysicalNetworkServiceProviderVO nsp =
+                        new 
PhysicalNetworkServiceProviderVO(physicalNetwork.getId(), extension.getName());
+                applyServicesToNsp(nsp, services);
+                nsp.setState(PhysicalNetworkServiceProvider.State.Enabled);
+                physicalNetworkServiceProviderDao.persist(nsp);
+                logger.info("Auto-created NetworkServiceProvider '{}' 
(Enabled) for physical network {} "
+                        + "with services {}", extension.getName(), 
physicalNetwork.getId(), services);
+            }
+
+            return savedExtensionMap;
+        });
+    }
+
+    /**
+     * Resolves the set of network service names declared in the extension's
+     * {@code network.services} detail. Falls back to an empty set if not 
present
+     */
+    private Set<String> resolveExtensionServices(Extension extension) {
+        Map<String, String> extDetails = 
extensionDetailsDao.listDetailsKeyPairs(extension.getId());
+        Set<String> parsed = parseNetworkServicesFromDetailKeys(extDetails);
+        if (!parsed.isEmpty()) {
+            return parsed;
+        }
+        // Default: the full set of services NetworkExtensionElement supports
+        return new HashSet<>();

Review Comment:
   `resolveExtensionServices` says it defaults to “the full set of services 
NetworkExtensionElement supports”, but currently returns an empty set. When 
`network.services` is not provided, this causes the auto-created 
`physical_network_service_providers` entry to have all `*_service_provided` 
flags set to false, which can prevent the extension provider from showing up in 
service/provider selection flows. Either enforce `network.services` as required 
for NetworkOrchestrator extensions, or actually default this to the supported 
service set (and set the NSP flags accordingly).



##########
ui/src/views/infra/network/ServiceProvidersTab.vue:
##########
@@ -170,10 +192,14 @@ export default {
       actionLoading: false,
       showFormAction: false,
       currentAction: {},
-      tabKey: 'BaremetalDhcpProvider'
+      tabKey: 'BaremetalDhcpProvider',
+      registeredExtensions: []
     }
   },
   computed: {
+    isExtensionTab () {
+      return this.registeredExtensions.some(ext => ext.name === this.tabKey)
+    },

Review Comment:
   The computed property `isExtensionTab` is currently unused (no references in 
template or methods). Consider removing it or wiring it into the logic that 
differentiates hardcoded vs extension-backed tabs to avoid dead code.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to