mirror of https://github.com/apache/cloudstack.git
Network Extension: Orchestrate external Network devices
This commit is contained in:
parent
5893ba5a8c
commit
b39eeac0d7
|
|
@ -854,6 +854,7 @@ public class EventTypes {
|
|||
public static final String EVENT_EXTENSION_DELETE = "EXTENSION.DELETE";
|
||||
public static final String EVENT_EXTENSION_RESOURCE_REGISTER = "EXTENSION.RESOURCE.REGISTER";
|
||||
public static final String EVENT_EXTENSION_RESOURCE_UNREGISTER = "EXTENSION.RESOURCE.UNREGISTER";
|
||||
public static final String EVENT_EXTENSION_RESOURCE_UPDATE = "EXTENSION.RESOURCE.UPDATE";
|
||||
public static final String EVENT_EXTENSION_CUSTOM_ACTION_ADD = "EXTENSION.CUSTOM.ACTION.ADD";
|
||||
public static final String EVENT_EXTENSION_CUSTOM_ACTION_UPDATE = "EXTENSION.CUSTOM.ACTION.UPDATE";
|
||||
public static final String EVENT_EXTENSION_CUSTOM_ACTION_DELETE = "EXTENSION.CUSTOM.ACTION.DELETE";
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ public interface Network extends ControlledEntity, StateObject<Network.State>, I
|
|||
|
||||
public static final Provider Nsx = new Provider("Nsx", false);
|
||||
public static final Provider Netris = new Provider("Netris", false);
|
||||
public static final Provider NetworkExtension = new Provider("NetworkExtension", false, true);
|
||||
|
||||
private final String name;
|
||||
private final boolean isExternal;
|
||||
|
|
@ -250,11 +251,47 @@ public interface Network extends ControlledEntity, StateObject<Network.State>, I
|
|||
return null;
|
||||
}
|
||||
|
||||
/** Private constructor for transient (non-registered) providers. */
|
||||
private Provider(String name) {
|
||||
this.name = name;
|
||||
this.isExternal = false;
|
||||
this.needCleanupOnShutdown = true;
|
||||
// intentionally NOT added to supportedProviders
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a transient (non-registered) {@link Provider} with the given name.
|
||||
*
|
||||
* <p>The new instance is <em>not</em> added to {@code supportedProviders}, so it
|
||||
* will never be returned by {@link #getProvider(String)} and will not pollute the
|
||||
* global provider registry. Use this for dynamic / extension-backed providers
|
||||
* whose names are only known at runtime (e.g. NetworkOrchestrator extensions).</p>
|
||||
*
|
||||
* @param name the provider name (typically the extension name)
|
||||
* @return a transient {@link Provider} instance with the given name
|
||||
*/
|
||||
public static Provider createTransientProvider(String name) {
|
||||
return new Provider(name);
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
||||
.append("name", name)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Provider)) return false;
|
||||
Provider provider = (Provider) obj;
|
||||
return this.name.equals(provider.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Capability {
|
||||
|
|
|
|||
|
|
@ -187,6 +187,8 @@ public interface NetworkModel {
|
|||
|
||||
boolean canElementEnableIndividualServices(Provider provider);
|
||||
|
||||
boolean canElementEnableIndividualServicesByName(String providerName);
|
||||
|
||||
boolean areServicesSupportedInNetwork(long networkId, Service... services);
|
||||
|
||||
boolean isNetworkSystem(Network network);
|
||||
|
|
@ -237,6 +239,18 @@ public interface NetworkModel {
|
|||
|
||||
String getDefaultGuestTrafficLabel(long dcId, HypervisorType vmware);
|
||||
|
||||
/**
|
||||
* Resolves a provider name to a {@link Provider} instance.
|
||||
* For known static providers, delegates to {@link Provider#getProvider(String)}.
|
||||
* For dynamically-registered NetworkOrchestrator extension providers whose names
|
||||
* are not in the static registry, returns a transient {@link Provider} with the
|
||||
* given name so callers can still dispatch correctly.
|
||||
*
|
||||
* @param providerName the provider name from {@code ntwk_service_map} or similar
|
||||
* @return a {@link Provider} instance, or {@code null} if not resolvable
|
||||
*/
|
||||
Provider resolveProvider(String providerName);
|
||||
|
||||
/**
|
||||
* @param providerName
|
||||
* @return
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ public interface NetworkService {
|
|||
|
||||
Pair<List<? extends PhysicalNetwork>, Integer> searchPhysicalNetworks(Long id, Long zoneId, String keyword, Long startIndex, Long pageSize, String name);
|
||||
|
||||
PhysicalNetwork updatePhysicalNetwork(Long id, String networkSpeed, List<String> tags, String newVnetRangeString, String state);
|
||||
PhysicalNetwork updatePhysicalNetwork(Long id, String networkSpeed, List<String> tags, String newVnetRangeString, String state, Map<String, String> externalDetails);
|
||||
|
||||
boolean deletePhysicalNetwork(Long id);
|
||||
|
||||
|
|
|
|||
|
|
@ -146,4 +146,8 @@ public interface NetworkElement extends Adapter {
|
|||
* @return true/false
|
||||
*/
|
||||
boolean verifyServicesCombination(Set<Service> services);
|
||||
|
||||
default boolean rollingRestartSupported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.cloudstack.api.command.admin.network;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
|
|
@ -53,6 +54,12 @@ public class UpdatePhysicalNetworkCmd extends BaseAsyncCmd {
|
|||
@Parameter(name = ApiConstants.VLAN, type = CommandType.STRING, description = "The VLAN for the physical Network")
|
||||
private String vlan;
|
||||
|
||||
@Parameter(name = ApiConstants.EXTERNAL_DETAILS,
|
||||
type = CommandType.MAP,
|
||||
description = "Details in key/value pairs to be added to the extension-resource mapping. Use the format externaldetails[i].<key>=<value>. Example: externaldetails[0].endpoint.url=https://example.com",
|
||||
since = "4.23.0")
|
||||
protected Map externalDetails;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -77,6 +84,10 @@ public class UpdatePhysicalNetworkCmd extends BaseAsyncCmd {
|
|||
return vlan;
|
||||
}
|
||||
|
||||
public Map<String, String> getExternalDetails() {
|
||||
return convertDetailsToMap(externalDetails);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -88,7 +99,7 @@ public class UpdatePhysicalNetworkCmd extends BaseAsyncCmd {
|
|||
|
||||
@Override
|
||||
public void execute() {
|
||||
PhysicalNetwork result = _networkService.updatePhysicalNetwork(getId(), getNetworkSpeed(), getTags(), getVlan(), getState());
|
||||
PhysicalNetwork result = _networkService.updatePhysicalNetwork(getId(), getNetworkSpeed(), getTags(), getVlan(), getState(), getExternalDetails());
|
||||
if (result != null) {
|
||||
PhysicalNetworkResponse response = _responseGenerator.createPhysicalNetworkResponse(result);
|
||||
response.setResponseName(getCommandName());
|
||||
|
|
|
|||
|
|
@ -62,4 +62,12 @@ public class CustomActionResultResponse extends BaseResponse {
|
|||
public void setResult(Map<String, String> result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Map<String, String> getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return Boolean.TRUE.equals(success);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ import org.apache.cloudstack.api.InternalIdentity;
|
|||
|
||||
public interface Extension extends InternalIdentity, Identity {
|
||||
enum Type {
|
||||
Orchestrator
|
||||
Orchestrator,
|
||||
NetworkOrchestrator
|
||||
}
|
||||
enum State {
|
||||
Enabled, Disabled;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ import com.google.gson.reflect.TypeToken;
|
|||
|
||||
public interface ExtensionCustomAction extends InternalIdentity, Identity {
|
||||
enum ResourceType {
|
||||
VirtualMachine(com.cloud.vm.VirtualMachine.class);
|
||||
VirtualMachine(com.cloud.vm.VirtualMachine.class),
|
||||
Network(com.cloud.network.Network.class);
|
||||
|
||||
private final Class<?> clazz;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,99 @@
|
|||
package org.apache.cloudstack.extension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.network.Network.Capability;
|
||||
import com.cloud.network.Network.Service;
|
||||
|
||||
public interface ExtensionHelper {
|
||||
Long getExtensionIdForCluster(long clusterId);
|
||||
Extension getExtension(long id);
|
||||
Extension getExtensionForCluster(long clusterId);
|
||||
List<String> getExtensionReservedResourceDetails(long extensionId);
|
||||
|
||||
/**
|
||||
* Detail key used to store the comma-separated list of network services provided
|
||||
* by a NetworkOrchestrator extension (e.g. {@code "SourceNat,StaticNat,Firewall"}).
|
||||
*/
|
||||
String NETWORK_SERVICES_DETAIL_KEY = "network.services";
|
||||
|
||||
/**
|
||||
* Detail key used to store a JSON object mapping each service name to its
|
||||
* CloudStack {@link com.cloud.network.Network.Capability} key/value pairs.
|
||||
* Example: {@code {"SourceNat":{"SupportedSourceNatTypes":"peraccount"}}}.
|
||||
* Used together with {@link #NETWORK_SERVICES_DETAIL_KEY}.
|
||||
*/
|
||||
String NETWORK_SERVICE_CAPABILITIES_DETAIL_KEY = "network.service.capabilities";
|
||||
|
||||
Long getExtensionIdForPhysicalNetwork(long physicalNetworkId);
|
||||
Extension getExtensionForPhysicalNetwork(long physicalNetworkId);
|
||||
String getExtensionScriptPath(Extension extension);
|
||||
Map<String, String> getExtensionDetails(long extensionId);
|
||||
|
||||
/**
|
||||
* Finds the extension registered with the given physical network whose name
|
||||
* matches the given provider name (case-insensitive). Returns {@code null}
|
||||
* if no matching extension is found.
|
||||
*
|
||||
* <p>This is the preferred lookup when multiple extensions are registered on
|
||||
* the same physical network: the provider name stored in
|
||||
* {@code ntwk_service_map} is used to pinpoint the exact extension that
|
||||
* handles a given network.</p>
|
||||
*
|
||||
* @param physicalNetworkId the physical network ID
|
||||
* @param providerName the provider name (must equal the extension name)
|
||||
* @return the matching {@link Extension}, or {@code null}
|
||||
*/
|
||||
Extension getExtensionForPhysicalNetworkAndProvider(long physicalNetworkId, String providerName);
|
||||
|
||||
/**
|
||||
* Returns ALL {@code extension_resource_map_details} (including hidden) for
|
||||
* the specific extension registered on the given physical network. Used by
|
||||
* {@code NetworkExtensionElement} to inject device credentials into the script
|
||||
* environment for the correct extension when multiple different extensions are
|
||||
* registered on the same physical network.
|
||||
*
|
||||
* @param physicalNetworkId the physical network ID
|
||||
* @param extensionId the extension ID
|
||||
* @return all key/value details including non-display ones, or an empty map
|
||||
*/
|
||||
Map<String, String> getAllResourceMapDetailsForExtensionOnPhysicalNetwork(long physicalNetworkId, long extensionId);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given provider name is backed by a
|
||||
* {@code NetworkOrchestrator} extension registered on any physical network.
|
||||
* This is used by {@code NetworkModelImpl} to detect extension-backed providers
|
||||
* that are not in the static {@code s_providerToNetworkElementMap}.
|
||||
*
|
||||
* @param providerName the provider / extension name
|
||||
* @return true if the provider is a NetworkExtension provider
|
||||
*/
|
||||
boolean isNetworkExtensionProvider(String providerName);
|
||||
|
||||
/**
|
||||
* List all registered extensions filtered by extension {@link Extension.Type}.
|
||||
* Useful for callers that need to discover available providers of a given
|
||||
* type (e.g. Orchestrator, NetworkOrchestrator).
|
||||
*
|
||||
* @param type extension type to filter by
|
||||
* @return list of matching {@link Extension} instances (empty list if none)
|
||||
*/
|
||||
List<Extension> listExtensionsByType(Extension.Type type);
|
||||
|
||||
/**
|
||||
* Returns the effective {@link Service} → ({@link Capability} → value) capabilities
|
||||
* for the given external network provider, looking it up by name on the given
|
||||
* physical network.
|
||||
*
|
||||
* <p>If {@code physicalNetworkId} is {@code null}, the method searches across all
|
||||
* physical networks that have extensions registered and returns the capabilities for
|
||||
* the first matching extension.</p>
|
||||
*
|
||||
* @param physicalNetworkId physical network ID, or {@code null} for offering-level queries
|
||||
* @param providerName provider / extension name
|
||||
* @return capabilities map, or the default capabilities if no matching extension is found
|
||||
*/
|
||||
Map<Service, Map<Capability, String>> getNetworkCapabilitiesForProvider(Long physicalNetworkId, String providerName);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ import org.apache.cloudstack.api.InternalIdentity;
|
|||
|
||||
public interface ExtensionResourceMap extends InternalIdentity, Identity {
|
||||
enum ResourceType {
|
||||
Cluster
|
||||
Cluster,
|
||||
PhysicalNetwork
|
||||
}
|
||||
|
||||
long getExtensionId();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.extension;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.network.Network;
|
||||
|
||||
/**
|
||||
* Implemented by network elements that support running custom actions on a
|
||||
* managed network (e.g. NetworkExtensionElement).
|
||||
*
|
||||
* <p>This interface is looked up by {@code ExtensionsManagerImpl} to dispatch
|
||||
* {@code runCustomAction} requests whose resource type is {@code Network}.</p>
|
||||
*/
|
||||
public interface NetworkCustomActionProvider {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this provider handles networks whose physical
|
||||
* network has an ExternalNetwork service provider registered.
|
||||
*
|
||||
* @param network the target network
|
||||
* @return {@code true} if this provider can handle the network
|
||||
*/
|
||||
boolean canHandleCustomAction(Network network);
|
||||
|
||||
/**
|
||||
* Runs a named custom action against the external network device that
|
||||
* manages the given network.
|
||||
*
|
||||
* @param network the CloudStack network on which to run the action
|
||||
* @param actionName the action name (e.g. {@code "reboot-device"}, {@code "dump-config"})
|
||||
* @param parameters optional parameters supplied by the caller
|
||||
* @return output from the action script, or {@code null} on failure
|
||||
*/
|
||||
String runCustomAction(Network network, String actionName, Map<String, Object> parameters);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.cloud.network;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
public class NetworkTest {
|
||||
|
||||
@Test
|
||||
public void testProviderContains() {
|
||||
List<Network.Provider> providers = new ArrayList<>();
|
||||
providers.add(Network.Provider.VirtualRouter);
|
||||
|
||||
// direct instance present
|
||||
assertTrue("List should contain VirtualRouter provider", providers.contains(Network.Provider.VirtualRouter));
|
||||
|
||||
// resolved provider by name (registered provider)
|
||||
Network.Provider resolved = Network.Provider.getProvider("VirtualRouter");
|
||||
assertNotNull("Resolved provider should not be null", resolved);
|
||||
assertTrue("List should contain resolved VirtualRouter provider", providers.contains(resolved));
|
||||
|
||||
// transient provider with same name should be considered equal (equals by name)
|
||||
Network.Provider transientProvider = Network.Provider.createTransientProvider("NetworkExtension");
|
||||
assertFalse("List should not contain the transient provider", providers.contains(transientProvider));
|
||||
|
||||
providers.add(transientProvider);
|
||||
assertTrue("List should contain the transient provider", providers.contains(transientProvider));
|
||||
|
||||
// another transient provider with same name should be considered equal
|
||||
Network.Provider transientProviderNew = Network.Provider.createTransientProvider("NetworkExtension");
|
||||
assertTrue("List should contain the new transient provider with same name", providers.contains(transientProviderNew));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -40,6 +40,12 @@ public class ExtensionCustomActionTest {
|
|||
assertEquals(com.cloud.vm.VirtualMachine.class, vmType.getAssociatedClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkResourceType() {
|
||||
ExtensionCustomAction.ResourceType networkType = ExtensionCustomAction.ResourceType.Network;
|
||||
assertEquals(com.cloud.network.Network.class, networkType.getAssociatedClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterTypeSupportsOptions() {
|
||||
assertTrue(ExtensionCustomAction.Parameter.Type.STRING.canSupportsOptions());
|
||||
|
|
|
|||
|
|
@ -177,6 +177,10 @@ import com.cloud.network.dao.PhysicalNetworkVO;
|
|||
import com.cloud.network.dao.RemoteAccessVpnDao;
|
||||
import com.cloud.network.dao.RemoteAccessVpnVO;
|
||||
import com.cloud.network.dao.RouterNetworkDao;
|
||||
import org.apache.cloudstack.extension.Extension;
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
import org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement;
|
||||
|
||||
import com.cloud.network.element.AggregatedCommandExecutor;
|
||||
import com.cloud.network.element.ConfigDriveNetworkElement;
|
||||
import com.cloud.network.element.DhcpServiceProvider;
|
||||
|
|
@ -368,6 +372,10 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
private BGPService bgpService;
|
||||
@Inject
|
||||
private Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao;
|
||||
@Inject
|
||||
protected ExtensionHelper extensionHelper;
|
||||
@Inject
|
||||
private NetworkExtensionElement networkExtensionElement;
|
||||
|
||||
@Override
|
||||
public List<NetworkGuru> getNetworkGurus() {
|
||||
|
|
@ -461,6 +469,28 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
|
||||
HashMap<Long, Long> _lastNetworkIdsToFree = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Returns the full list of network elements to iterate when implementing,
|
||||
* shutting down, or otherwise orchestrating a network.
|
||||
*
|
||||
* <p>The base list ({@link #networkElements}, wired by Spring) is extended
|
||||
* at runtime with one transient {@link NetworkExtensionElement} per
|
||||
* registered {@code NetworkOrchestrator} extension. This keeps the
|
||||
* Spring bean list free from {@code NetworkExtensionElement} and allows
|
||||
* dynamic discovery of extensions without a restart.</p>
|
||||
*/
|
||||
private List<NetworkElement> getNetworkElementsIncludingExtensions() {
|
||||
List<Extension> extensions = extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator);
|
||||
if (extensions == null || extensions.isEmpty()) {
|
||||
return networkElements;
|
||||
}
|
||||
List<NetworkElement> combined = new ArrayList<>(networkElements);
|
||||
for (Extension ext : extensions) {
|
||||
combined.add(networkExtensionElement.withProviderName(ext.getName()));
|
||||
}
|
||||
return combined;
|
||||
}
|
||||
|
||||
private void updateRouterDefaultDns(final VirtualMachineProfile vmProfile, final NicProfile nicProfile) {
|
||||
if (!Type.DomainRouter.equals(vmProfile.getType()) || !nicProfile.isDefaultNic()) {
|
||||
return;
|
||||
|
|
@ -1686,7 +1716,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
}
|
||||
}
|
||||
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) {
|
||||
((AggregatedCommandExecutor) element).prepareAggregatedExecution(network, dest);
|
||||
}
|
||||
|
|
@ -1703,7 +1733,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
ex.addProxyObject(_entityMgr.findById(DataCenter.class, network.getDataCenterId()).getUuid());
|
||||
throw ex;
|
||||
}
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) {
|
||||
if (!((AggregatedCommandExecutor) element).completeAggregatedExecution(network, dest)) {
|
||||
logger.warn("Failed to re-program the network as a part of network {} implement due to aggregated commands execution failure!", network);
|
||||
|
|
@ -1717,7 +1747,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
}
|
||||
reconfigureAndApplyStaticRouteForVpcVpn(network);
|
||||
} finally {
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) {
|
||||
((AggregatedCommandExecutor) element).cleanupAggregatedExecution(network, dest);
|
||||
}
|
||||
|
|
@ -1738,7 +1768,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
|
||||
private void implementNetworkElements(final DeployDestination dest, final ReservationContext context, final Network network, final NetworkOffering offering, final List<Provider> providersToImplement)
|
||||
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
|
||||
for (NetworkElement element : networkElements) {
|
||||
for (NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToImplement.contains(element.getProvider())) {
|
||||
if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) {
|
||||
// The physicalNetworkId will not get translated into a uuid by the response serializer,
|
||||
|
|
@ -2031,7 +2061,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
@Override
|
||||
public void configureUpdateInSequence(Network network) {
|
||||
List<Provider> providers = getNetworkProviders(network.getId());
|
||||
for (NetworkElement element : networkElements) {
|
||||
for (NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providers.contains(element.getProvider())) {
|
||||
if (element instanceof RedundantResource) {
|
||||
((RedundantResource) element).configureResource(network);
|
||||
|
|
@ -2044,7 +2074,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
public int getResourceCount(Network network) {
|
||||
List<Provider> providers = getNetworkProviders(network.getId());
|
||||
int resourceCount = 0;
|
||||
for (NetworkElement element : networkElements) {
|
||||
for (NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providers.contains(element.getProvider())) {
|
||||
//currently only one element implements the redundant resource interface
|
||||
if (element instanceof RedundantResource) {
|
||||
|
|
@ -2075,7 +2105,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
@Override
|
||||
public void finalizeUpdateInSequence(Network network, boolean success) {
|
||||
List<Provider> providers = getNetworkProviders(network.getId());
|
||||
for (NetworkElement element : networkElements) {
|
||||
for (NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providers.contains(element.getProvider())) {
|
||||
//currently only one element implements the redundant resource interface
|
||||
if (element instanceof RedundantResource) {
|
||||
|
|
@ -2102,7 +2132,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
}
|
||||
|
||||
private void setHypervisorHostnameInNetwork(VirtualMachineProfile vm, DeployDestination dest, Network network, NicProfile profile, boolean migrationSuccessful) {
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData) && element instanceof UserDataServiceProvider
|
||||
&& (element instanceof ConfigDriveNetworkElement && !migrationSuccessful || element instanceof VirtualRouterElement && migrationSuccessful)) {
|
||||
String errorMsg = String.format("Failed to add hypervisor host name while applying the userdata during the migration of VM %s, " +
|
||||
|
|
@ -2230,7 +2260,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
updateNic(nic, network, 1);
|
||||
|
||||
final List<Provider> providersToImplement = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToImplement.contains(element.getProvider())) {
|
||||
if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) {
|
||||
throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: "
|
||||
|
|
@ -2285,7 +2315,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
}
|
||||
|
||||
final List<Provider> providersToImplement = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToImplement.contains(element.getProvider())) {
|
||||
if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) {
|
||||
throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: "
|
||||
|
|
@ -2329,7 +2359,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
}
|
||||
}
|
||||
final List<Provider> providersToImplement = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToImplement.contains(element.getProvider())) {
|
||||
if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) {
|
||||
throw new CloudRuntimeException(String.format("Service provider %s either doesn't exist or is not enabled in physical network: %s",
|
||||
|
|
@ -2411,7 +2441,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
}
|
||||
|
||||
final List<Provider> providersToImplement = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToImplement.contains(element.getProvider())) {
|
||||
if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) {
|
||||
throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: "
|
||||
|
|
@ -2447,7 +2477,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
}
|
||||
|
||||
final List<Provider> providersToImplement = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToImplement.contains(element.getProvider())) {
|
||||
if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) {
|
||||
throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: "
|
||||
|
|
@ -2534,7 +2564,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
final Network network = networkToRelease.first();
|
||||
final NicProfile profile = networkToRelease.second();
|
||||
final List<Provider> providersToImplement = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToImplement.contains(element.getProvider())) {
|
||||
logger.debug("Asking {} to release {}", element.getName(), profile);
|
||||
//NOTE: Context appear to never be used in release method
|
||||
|
|
@ -2597,7 +2627,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
*/
|
||||
if (nic.getReservationStrategy() == Nic.ReservationStrategy.Create) {
|
||||
final List<Provider> providersToImplement = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToImplement.contains(element.getProvider())) {
|
||||
logger.debug("Asking {} to release {}, according to the reservation strategy {}.", element.getName(), nic, nic.getReservationStrategy());
|
||||
try {
|
||||
|
|
@ -3328,7 +3358,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
|
||||
// 2) Shutdown all the network elements
|
||||
boolean success = true;
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToShutdown.contains(element.getProvider())) {
|
||||
try {
|
||||
logger.debug("Sending network shutdown to {}", element.getName());
|
||||
|
|
@ -3439,7 +3469,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
|
||||
// get providers to destroy
|
||||
final List<Provider> providersToDestroy = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (providersToDestroy.contains(element.getProvider())) {
|
||||
try {
|
||||
logger.debug("Sending destroy to {}", element);
|
||||
|
|
@ -3810,7 +3840,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
public void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile) {
|
||||
|
||||
final List<Provider> networkProviders = getNetworkProviders(network.getId());
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (networkProviders.contains(element.getProvider())) {
|
||||
if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) {
|
||||
throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: "
|
||||
|
|
@ -3846,7 +3876,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
* @throws InsufficientCapacityException
|
||||
*/
|
||||
private boolean rollingRestartRouters(final NetworkVO network, final NetworkOffering offering, final DeployDestination dest, final ReservationContext context) throws ResourceUnavailableException, ConcurrentOperationException, InsufficientCapacityException {
|
||||
if (!NetworkOrchestrationService.RollingRestartEnabled.value()) {
|
||||
if (!isRollingRestartSupport(network)) {
|
||||
if (shutdownNetworkElementsAndResources(context, true, network)) {
|
||||
implementNetworkElementsAndResources(dest, context, network, offering);
|
||||
return true;
|
||||
|
|
@ -3894,6 +3924,20 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
return areRoutersRunning(routerDao.findByNetwork(network.getId()));
|
||||
}
|
||||
|
||||
private boolean isRollingRestartSupport(final NetworkVO network) {
|
||||
if (!NetworkOrchestrator.RollingRestartEnabled.value()) {
|
||||
return false;
|
||||
}
|
||||
List<NetworkServiceMapVO> services = _ntwkSrvcDao.getServicesInNetwork(network.getId());
|
||||
for (NetworkServiceMapVO service : services) {
|
||||
NetworkElement element = _networkModel.getElementImplementingProvider(service.getProvider());
|
||||
if (element == null || !element.rollingRestartSupported()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setRestartRequired(final NetworkVO network, final boolean restartRequired) {
|
||||
logger.debug("Marking network {} with restartRequired={}", network, restartRequired);
|
||||
network.setRestartRequired(restartRequired);
|
||||
|
|
@ -4455,6 +4499,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
|
||||
if (provider == null) {
|
||||
provider = _networkModel.getDefaultUniqueProviderForService(service).getName();
|
||||
} else {
|
||||
provider = _networkModel.resolveProvider(provider).getName();
|
||||
}
|
||||
|
||||
// check that provider is supported
|
||||
|
|
@ -4480,7 +4526,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
final List<String> providerNames = _ntwkSrvcDao.getDistinctProviders(networkId);
|
||||
final List<Provider> providers = new ArrayList<>();
|
||||
for (final String providerName : providerNames) {
|
||||
providers.add(Network.Provider.getProvider(providerName));
|
||||
providers.add(_networkModel.resolveProvider(providerName));
|
||||
}
|
||||
|
||||
return providers;
|
||||
|
|
@ -4646,7 +4692,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
if (providers == null) {
|
||||
providers = new HashSet<>();
|
||||
}
|
||||
providers.add(Provider.getProvider(nsm.getProvider()));
|
||||
providers.add(_networkModel.resolveProvider(nsm.getProvider()));
|
||||
map.put(Service.getService(nsm.getService()), providers);
|
||||
}
|
||||
return map;
|
||||
|
|
@ -4931,10 +4977,10 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
|||
|
||||
@Override
|
||||
public void expungeLbVmRefs(List<Long> vmIds, Long batchSize) {
|
||||
if (CollectionUtils.isEmpty(networkElements) || CollectionUtils.isEmpty(vmIds)) {
|
||||
if (CollectionUtils.isEmpty(getNetworkElementsIncludingExtensions()) || CollectionUtils.isEmpty(vmIds)) {
|
||||
return;
|
||||
}
|
||||
for (NetworkElement element : networkElements) {
|
||||
for (NetworkElement element : getNetworkElementsIncludingExtensions()) {
|
||||
if (element instanceof LoadBalancingServiceProvider) {
|
||||
LoadBalancingServiceProvider lbProvider = (LoadBalancingServiceProvider)element;
|
||||
lbProvider.expungeLbVmRefs(vmIds, batchSize);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import com.cloud.dc.DataCenter;
|
|||
import com.cloud.exception.InsufficientVirtualNetworkCapacityException;
|
||||
import com.cloud.network.IpAddressManager;
|
||||
import com.cloud.utils.Pair;
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
|
@ -135,6 +136,7 @@ public class NetworkOrchestratorTest extends TestCase {
|
|||
testOrchestrator.routerJoinDao = mock(DomainRouterJoinDao.class);
|
||||
testOrchestrator._ipAddrMgr = mock(IpAddressManager.class);
|
||||
testOrchestrator._entityMgr = mock(EntityManager.class);
|
||||
testOrchestrator.extensionHelper = mock(ExtensionHelper.class);
|
||||
DhcpServiceProvider provider = mock(DhcpServiceProvider.class);
|
||||
|
||||
Map<Network.Capability, String> capabilities = new HashMap<Network.Capability, String>();
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import org.springframework.stereotype.Component;
|
|||
import com.cloud.network.Network;
|
||||
import com.cloud.network.Network.Event;
|
||||
import com.cloud.network.Network.GuestType;
|
||||
import com.cloud.network.Network.Provider;
|
||||
import com.cloud.network.Network.Service;
|
||||
import com.cloud.network.Network.State;
|
||||
import com.cloud.network.Networks.BroadcastDomainType;
|
||||
|
|
@ -390,7 +389,7 @@ public class NetworkDaoImpl extends GenericDaoBase<NetworkVO, Long>implements Ne
|
|||
final TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||
txn.start();
|
||||
for (final String service : serviceProviderMap.keySet()) {
|
||||
final NetworkServiceMapVO serviceMap = new NetworkServiceMapVO(networkId, Service.getService(service), Provider.getProvider(serviceProviderMap.get(service)));
|
||||
final NetworkServiceMapVO serviceMap = new NetworkServiceMapVO(networkId, Service.getService(service).getName(), serviceProviderMap.get(service));
|
||||
_ntwkSvcMap.persist(serviceMap);
|
||||
}
|
||||
txn.commit();
|
||||
|
|
|
|||
|
|
@ -81,6 +81,12 @@ public class NetworkServiceMapVO implements InternalIdentity {
|
|||
this.provider = provider.getName();
|
||||
}
|
||||
|
||||
public NetworkServiceMapVO(long networkId, String serviceName, String providerName) {
|
||||
this.networkId = networkId;
|
||||
this.service = serviceName;
|
||||
this.provider = providerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder("[Network Service[");
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ import javax.persistence.GenerationType;
|
|||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import com.cloud.network.Network.Provider;
|
||||
import com.cloud.network.Network.Service;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
|
||||
@Entity
|
||||
|
|
@ -72,10 +70,10 @@ public class VpcServiceMapVO {
|
|||
public VpcServiceMapVO() {
|
||||
}
|
||||
|
||||
public VpcServiceMapVO(long vpcId, Service service, Provider provider) {
|
||||
public VpcServiceMapVO(long vpcId, String serviceName, String providerName) {
|
||||
this.vpcId = vpcId;
|
||||
this.service = service.getName();
|
||||
this.provider = provider.getName();
|
||||
this.service = serviceName;
|
||||
this.provider = providerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ public class VpcDaoImpl extends GenericDaoBase<VpcVO, Long> implements VpcDao {
|
|||
txn.start();
|
||||
for (String service : serviceProviderMap.keySet()) {
|
||||
for (String provider : serviceProviderMap.get(service)) {
|
||||
VpcServiceMapVO serviceMap = new VpcServiceMapVO(vpcId, Network.Service.getService(service), Network.Provider.getProvider(provider));
|
||||
VpcServiceMapVO serviceMap = new VpcServiceMapVO(vpcId, Network.Service.getService(service).getName(), provider);
|
||||
_vpcSvcMap.persist(serviceMap);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,3 +131,7 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` (
|
|||
-- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1');
|
||||
|
||||
-- Increase length of value of extension details from 255 to 4096 to support longer details value
|
||||
CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.extension_details', 'value', 'value', 'VARCHAR(4096)');
|
||||
CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.extension_resource_map_details', 'value', 'value', 'VARCHAR(4096)');
|
||||
|
|
|
|||
|
|
@ -70,6 +70,17 @@ public class ListExtensionsCmd extends BaseListCmd {
|
|||
+ " When no parameters are passed, all the details are returned.")
|
||||
private List<String> details;
|
||||
|
||||
@Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description = "Type of the extension (e.g. Orchestrator, NetworkOrchestrator). Default is Orchestrator if not set")
|
||||
private String type;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING,
|
||||
description = "ID of the resource to list registered extensions for (e.g. cluster UUID, physical network UUID)")
|
||||
private String resourceId;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING,
|
||||
description = "Type of the resource (e.g. Cluster, PhysicalNetwork). Default is Cluster if not set")
|
||||
private String resourceType;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
@ -82,6 +93,18 @@ public class ListExtensionsCmd extends BaseListCmd {
|
|||
return extensionId;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public String getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public EnumSet<ApiConstants.ExtensionDetails> getDetails() throws InvalidParameterValueException {
|
||||
if (CollectionUtils.isEmpty(details)) {
|
||||
return EnumSet.of(ApiConstants.ExtensionDetails.all);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.framework.extensions.api;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.ExtensionResponse;
|
||||
import org.apache.cloudstack.extension.Extension;
|
||||
import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager;
|
||||
|
||||
import com.cloud.user.Account;
|
||||
|
||||
@APICommand(name = "updateRegisteredExtension",
|
||||
description = "Update details for an extension registered with a resource",
|
||||
responseObject = ExtensionResponse.class,
|
||||
responseHasSensitiveInfo = false,
|
||||
entityType = {Extension.class},
|
||||
authorized = {RoleType.Admin},
|
||||
since = "4.23.0")
|
||||
public class UpdateRegisteredExtensionCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
ExtensionsManager extensionsManager;
|
||||
|
||||
@Parameter(name = ApiConstants.EXTENSION_ID, type = CommandType.UUID, required = true,
|
||||
entityType = ExtensionResponse.class, description = "ID of the extension")
|
||||
private Long extensionId;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true,
|
||||
description = "ID of the resource where the extension is registered")
|
||||
private String resourceId;
|
||||
|
||||
@Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true,
|
||||
description = "Type of the resource")
|
||||
private String resourceType;
|
||||
|
||||
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP,
|
||||
description = "Details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].endpoint.url=urlvalue")
|
||||
protected Map details;
|
||||
|
||||
@Parameter(name = ApiConstants.CLEAN_UP_DETAILS,
|
||||
type = CommandType.BOOLEAN,
|
||||
description = "Optional boolean field, which indicates if details should be cleaned up or not " +
|
||||
"(If set to true, details removed for this registration, details field ignored; " +
|
||||
"if false or not set, details can be updated through details map)")
|
||||
private Boolean cleanupDetails;
|
||||
|
||||
public Long getExtensionId() {
|
||||
return extensionId;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public String getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public Map<String, String> getDetails() {
|
||||
return convertDetailsToMap(details);
|
||||
}
|
||||
|
||||
public Boolean isCleanupDetails() {
|
||||
return cleanupDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ServerApiException {
|
||||
Extension extension = extensionsManager.updateRegisteredExtensionWithResource(this);
|
||||
ExtensionResponse response = extensionsManager.createExtensionResponse(extension,
|
||||
EnumSet.of(ApiConstants.ExtensionDetails.all));
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return Account.ACCOUNT_ID_SYSTEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.Extension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return getExtensionId();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -16,6 +16,9 @@
|
|||
// under the License.
|
||||
package org.apache.cloudstack.framework.extensions.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.extension.Extension;
|
||||
import org.apache.cloudstack.framework.extensions.vo.ExtensionVO;
|
||||
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
|
|
@ -23,4 +26,6 @@ import com.cloud.utils.db.GenericDao;
|
|||
public interface ExtensionDao extends GenericDao<ExtensionVO, Long> {
|
||||
|
||||
ExtensionVO findByName(String name);
|
||||
|
||||
List<ExtensionVO> listByType(Extension.Type type);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
package org.apache.cloudstack.framework.extensions.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.extension.Extension;
|
||||
import org.apache.cloudstack.framework.extensions.vo.ExtensionVO;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
|
|
@ -39,7 +42,13 @@ public class ExtensionDaoImpl extends GenericDaoBase<ExtensionVO, Long> implemen
|
|||
public ExtensionVO findByName(String name) {
|
||||
SearchCriteria<ExtensionVO> sc = AllFieldSearch.create();
|
||||
sc.setParameters("name", name);
|
||||
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionVO> listByType(Extension.Type type) {
|
||||
SearchCriteria<ExtensionVO> sc = AllFieldSearch.create();
|
||||
sc.setParameters("type", type);
|
||||
return listBy(sc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,5 +28,9 @@ public interface ExtensionResourceMapDao extends GenericDao<ExtensionResourceMap
|
|||
|
||||
ExtensionResourceMapVO findByResourceIdAndType(long resourceId, ExtensionResourceMap.ResourceType resourceType);
|
||||
|
||||
List<Long> listResourceIdsByExtensionIdAndType(long extensionId,ExtensionResourceMap.ResourceType resourceType);
|
||||
List<ExtensionResourceMapVO> listByResourceIdAndType(long resourceId, ExtensionResourceMap.ResourceType resourceType);
|
||||
|
||||
List<Long> listResourceIdsByExtensionIdAndType(long extensionId, ExtensionResourceMap.ResourceType resourceType);
|
||||
|
||||
List<Long> listResourceIdsByType(ExtensionResourceMap.ResourceType resourceType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,15 @@ public class ExtensionResourceMapDaoImpl extends GenericDaoBase<ExtensionResourc
|
|||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExtensionResourceMapVO> listByResourceIdAndType(long resourceId,
|
||||
ExtensionResourceMap.ResourceType resourceType) {
|
||||
SearchCriteria<ExtensionResourceMapVO> sc = genericSearch.create();
|
||||
sc.setParameters("resourceId", resourceId);
|
||||
sc.setParameters("resourceType", resourceType);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listResourceIdsByExtensionIdAndType(long extensionId, ExtensionResourceMap.ResourceType resourceType) {
|
||||
GenericSearchBuilder<ExtensionResourceMapVO, Long> sb = createSearchBuilder(Long.class);
|
||||
|
|
@ -67,4 +76,15 @@ public class ExtensionResourceMapDaoImpl extends GenericDaoBase<ExtensionResourc
|
|||
sc.setParameters("resourceType", resourceType);
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listResourceIdsByType(ExtensionResourceMap.ResourceType resourceType) {
|
||||
GenericSearchBuilder<ExtensionResourceMapVO, Long> sb = createSearchBuilder(Long.class);
|
||||
sb.selectFields(sb.entity().getResourceId());
|
||||
sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||
sb.done();
|
||||
SearchCriteria<Long> sc = sb.create();
|
||||
sc.setParameters("resourceType", resourceType);
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import org.apache.cloudstack.framework.extensions.api.RunCustomActionCmd;
|
|||
import org.apache.cloudstack.framework.extensions.api.UnregisterExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateCustomActionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateRegisteredExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.command.ExtensionServerActionBaseCommand;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
|
|
@ -65,6 +66,8 @@ public interface ExtensionsManager extends Manager {
|
|||
|
||||
Extension unregisterExtensionWithResource(UnregisterExtensionCmd cmd);
|
||||
|
||||
Extension updateRegisteredExtensionWithResource(UpdateRegisteredExtensionCmd cmd);
|
||||
|
||||
Extension updateExtension(UpdateExtensionCmd cmd);
|
||||
|
||||
Extension registerExtensionWithResource(RegisterExtensionCmd cmd);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import java.nio.file.Path;
|
|||
import java.nio.file.Paths;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
|
@ -61,6 +62,7 @@ import org.apache.cloudstack.extension.CustomActionResultResponse;
|
|||
import org.apache.cloudstack.extension.Extension;
|
||||
import org.apache.cloudstack.extension.ExtensionCustomAction;
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
import org.apache.cloudstack.extension.NetworkCustomActionProvider;
|
||||
import org.apache.cloudstack.extension.ExtensionResourceMap;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
|
|
@ -75,6 +77,7 @@ import org.apache.cloudstack.framework.extensions.api.RunCustomActionCmd;
|
|||
import org.apache.cloudstack.framework.extensions.api.UnregisterExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateCustomActionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateRegisteredExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.command.CleanupExtensionFilesCommand;
|
||||
import org.apache.cloudstack.framework.extensions.command.ExtensionRoutingUpdateCommand;
|
||||
import org.apache.cloudstack.framework.extensions.command.ExtensionServerActionBaseCommand;
|
||||
|
|
@ -125,6 +128,19 @@ import com.cloud.host.dao.HostDao;
|
|||
import com.cloud.host.dao.HostDetailsDao;
|
||||
import com.cloud.hypervisor.ExternalProvisioner;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.Network.Capability;
|
||||
import com.cloud.network.Network.Service;
|
||||
import com.cloud.network.NetworkModel;
|
||||
import com.cloud.network.PhysicalNetworkServiceProvider;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
import com.cloud.network.dao.NetworkServiceMapDao;
|
||||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
|
||||
import com.cloud.network.dao.PhysicalNetworkServiceProviderVO;
|
||||
import com.cloud.network.element.NetworkElement;
|
||||
import com.cloud.network.dao.PhysicalNetworkDao;
|
||||
import com.cloud.network.dao.PhysicalNetworkVO;
|
||||
import com.cloud.org.Cluster;
|
||||
import com.cloud.serializer.GsonHelper;
|
||||
import com.cloud.storage.dao.VMTemplateDao;
|
||||
|
|
@ -141,6 +157,10 @@ import com.cloud.utils.db.SearchBuilder;
|
|||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.Transaction;
|
||||
import com.cloud.utils.db.TransactionCallbackWithException;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachineManager;
|
||||
|
|
@ -171,6 +191,12 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
@Inject
|
||||
ClusterDao clusterDao;
|
||||
|
||||
@Inject
|
||||
PhysicalNetworkDao physicalNetworkDao;
|
||||
|
||||
@Inject
|
||||
PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao;
|
||||
|
||||
@Inject
|
||||
AgentManager agentMgr;
|
||||
|
||||
|
|
@ -210,6 +236,15 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
@Inject
|
||||
VMTemplateDao templateDao;
|
||||
|
||||
@Inject
|
||||
NetworkDao networkDao;
|
||||
|
||||
@Inject
|
||||
NetworkServiceMapDao networkServiceMapDao;
|
||||
|
||||
@Inject
|
||||
NetworkModel networkModel;
|
||||
|
||||
@Inject
|
||||
RoleService roleService;
|
||||
|
||||
|
|
@ -339,6 +374,39 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
return getResultFromAnswersString(answersStr, extension, msHost, "get path checksum");
|
||||
}
|
||||
|
||||
protected List<ExtensionResourceMapDetailsVO> buildExtensionResourceDetailsArray(long extensionResourceMapId,
|
||||
Map<String, String> details) {
|
||||
List<ExtensionResourceMapDetailsVO> detailsList = new ArrayList<>();
|
||||
if (MapUtils.isEmpty(details)) {
|
||||
return detailsList;
|
||||
}
|
||||
for (Map.Entry<String, String> entry : details.entrySet()) {
|
||||
boolean display = !SENSITIVE_DETAIL_KEYS.contains(entry.getKey().toLowerCase());
|
||||
detailsList.add(new ExtensionResourceMapDetailsVO(extensionResourceMapId, entry.getKey(),
|
||||
entry.getValue(), display));
|
||||
}
|
||||
return detailsList;
|
||||
}
|
||||
|
||||
protected void appendHiddenExtensionResourceDetails(long extensionResourceMapId,
|
||||
List<ExtensionResourceMapDetailsVO> detailsList) {
|
||||
if (CollectionUtils.isEmpty(detailsList)) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> hiddenDetails = extensionResourceMapDetailsDao.listDetailsKeyPairs(extensionResourceMapId, false);
|
||||
if (MapUtils.isEmpty(hiddenDetails)) {
|
||||
return;
|
||||
}
|
||||
Set<String> requestedKeys = detailsList.stream()
|
||||
.map(ExtensionResourceMapDetailsVO::getName)
|
||||
.collect(Collectors.toSet());
|
||||
hiddenDetails.forEach((key, value) -> {
|
||||
if (!requestedKeys.contains(key)) {
|
||||
detailsList.add(new ExtensionResourceMapDetailsVO(extensionResourceMapId, key, value, false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected List<ExtensionCustomAction.Parameter> getParametersListFromMap(String actionName, Map parametersMap) {
|
||||
if (MapUtils.isEmpty(parametersMap)) {
|
||||
return Collections.emptyList();
|
||||
|
|
@ -370,16 +438,42 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
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) {
|
||||
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());
|
||||
}
|
||||
if (clusterId == null) {
|
||||
return null;
|
||||
}
|
||||
ExtensionResourceMapVO mapVO =
|
||||
extensionResourceMapDao.findByResourceIdAndType(clusterId, ExtensionResourceMap.ResourceType.Cluster);
|
||||
if (mapVO == null) {
|
||||
return null;
|
||||
}
|
||||
return extensionDao.findById(mapVO.getExtensionId());
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getActionMessage(boolean success, ExtensionCustomAction action, Extension extension,
|
||||
|
|
@ -694,25 +788,68 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
Long id = cmd.getExtensionId();
|
||||
String name = cmd.getName();
|
||||
String keyword = cmd.getKeyword();
|
||||
String typeStr = cmd.getType();
|
||||
String resourceIdStr = cmd.getResourceId();
|
||||
String resourceTypeStr = cmd.getResourceType();
|
||||
|
||||
// If resourceId + resourceType are specified, return only extensions registered to that resource
|
||||
if (StringUtils.isNotBlank(resourceIdStr) && StringUtils.isNotBlank(resourceTypeStr)) {
|
||||
if (!EnumUtils.isValidEnum(ExtensionResourceMap.ResourceType.class, resourceTypeStr)) {
|
||||
throw new InvalidParameterValueException("Invalid resourcetype: " + resourceTypeStr);
|
||||
}
|
||||
ExtensionResourceMap.ResourceType resType = ExtensionResourceMap.ResourceType.valueOf(resourceTypeStr);
|
||||
// Resolve resourceId to a DB id
|
||||
long resolvedResourceId;
|
||||
if (ExtensionResourceMap.ResourceType.PhysicalNetwork.equals(resType)) {
|
||||
PhysicalNetworkVO pn = physicalNetworkDao.findByUuid(resourceIdStr);
|
||||
if (pn == null) {
|
||||
try { pn = physicalNetworkDao.findById(Long.parseLong(resourceIdStr)); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
if (pn == null) throw new InvalidParameterValueException("Invalid physical network ID: " + resourceIdStr);
|
||||
resolvedResourceId = pn.getId();
|
||||
} else {
|
||||
try { resolvedResourceId = Long.parseLong(resourceIdStr); } catch (NumberFormatException e) {
|
||||
throw new InvalidParameterValueException("Invalid resource ID: " + resourceIdStr);
|
||||
}
|
||||
}
|
||||
List<ExtensionResourceMapVO> maps = extensionResourceMapDao.listByResourceIdAndType(resolvedResourceId, resType);
|
||||
List<ExtensionResponse> responses = new ArrayList<>();
|
||||
for (ExtensionResourceMapVO map : maps) {
|
||||
ExtensionVO ext = extensionDao.findById(map.getExtensionId());
|
||||
if (ext == null) continue;
|
||||
if (typeStr != null && !typeStr.equalsIgnoreCase(ext.getType().name())) continue;
|
||||
if (name != null && !name.equalsIgnoreCase(ext.getName())) continue;
|
||||
responses.add(createExtensionResponse(ext, cmd.getDetails()));
|
||||
}
|
||||
return responses;
|
||||
}
|
||||
|
||||
final SearchBuilder<ExtensionVO> sb = extensionDao.createSearchBuilder();
|
||||
final Filter searchFilter = new Filter(ExtensionVO.class, "id", false, cmd.getStartIndex(), cmd.getPageSizeVal());
|
||||
|
||||
sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ);
|
||||
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
|
||||
sb.and("keyword", sb.entity().getName(), SearchCriteria.Op.LIKE);
|
||||
sb.and("type", sb.entity().getType(), SearchCriteria.Op.EQ);
|
||||
sb.done();
|
||||
final SearchCriteria<ExtensionVO> sc = sb.create();
|
||||
|
||||
if (id != null) {
|
||||
sc.setParameters("id", id);
|
||||
}
|
||||
|
||||
if (name != null) {
|
||||
sc.setParameters("name", name);
|
||||
}
|
||||
|
||||
if (keyword != null) {
|
||||
sc.setParameters("keyword", "%" + keyword + "%");
|
||||
}
|
||||
if (typeStr != null) {
|
||||
Extension.Type type = EnumUtils.getEnum(Extension.Type.class, typeStr);
|
||||
if (type == null) {
|
||||
throw new InvalidParameterValueException("Invalid type: " + typeStr);
|
||||
}
|
||||
sc.setParameters("type", type);
|
||||
}
|
||||
|
||||
final Pair<List<ExtensionVO>, Integer> result = extensionDao.searchAndCount(sc, searchFilter);
|
||||
List<ExtensionResponse> responses = new ArrayList<>();
|
||||
|
|
@ -880,21 +1017,110 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
String resourceType = cmd.getResourceType();
|
||||
if (!EnumUtils.isValidEnum(ExtensionResourceMap.ResourceType.class, resourceType)) {
|
||||
throw new InvalidParameterValueException(
|
||||
String.format("Currently only [%s] can be used to register an extension of type Orchestrator",
|
||||
String.format("Currently only [%s] can be used to register an extension",
|
||||
EnumSet.allOf(ExtensionResourceMap.ResourceType.class)));
|
||||
}
|
||||
ClusterVO clusterVO = clusterDao.findByUuid(resourceId);
|
||||
if (clusterVO == null) {
|
||||
throw new InvalidParameterValueException("Invalid cluster ID specified");
|
||||
}
|
||||
ExtensionVO extension = extensionDao.findById(extensionId);
|
||||
if (extension == null) {
|
||||
throw new InvalidParameterValueException("Invalid extension specified");
|
||||
}
|
||||
ExtensionResourceMap.ResourceType resType = ExtensionResourceMap.ResourceType.valueOf(resourceType);
|
||||
if (ExtensionResourceMap.ResourceType.PhysicalNetwork.equals(resType)) {
|
||||
PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findByUuid(resourceId);
|
||||
if (physicalNetwork == null) {
|
||||
physicalNetwork = physicalNetworkDao.findById(Long.parseLong(resourceId));
|
||||
}
|
||||
if (physicalNetwork == null) {
|
||||
throw new InvalidParameterValueException("Invalid physical network ID specified");
|
||||
}
|
||||
ExtensionResourceMap extensionResourceMap = registerExtensionWithPhysicalNetwork(physicalNetwork, extension, cmd.getDetails());
|
||||
return extensionDao.findById(extensionResourceMap.getExtensionId());
|
||||
}
|
||||
ClusterVO clusterVO = clusterDao.findByUuid(resourceId);
|
||||
if (clusterVO == null) {
|
||||
throw new InvalidParameterValueException("Invalid cluster ID specified");
|
||||
}
|
||||
ExtensionResourceMap extensionResourceMap = registerExtensionWithCluster(clusterVO, extension, cmd.getDetails());
|
||||
return extensionDao.findById(extensionResourceMap.getExtensionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_EXTENSION_RESOURCE_UPDATE, eventDescription = "updating extension resource")
|
||||
public Extension updateRegisteredExtensionWithResource(UpdateRegisteredExtensionCmd cmd) {
|
||||
final String resourceId = cmd.getResourceId();
|
||||
final Long extensionId = cmd.getExtensionId();
|
||||
final String resourceType = cmd.getResourceType();
|
||||
final Map<String, String> details = cmd.getDetails();
|
||||
final Boolean cleanupDetails = cmd.isCleanupDetails();
|
||||
|
||||
if (!EnumUtils.isValidEnum(ExtensionResourceMap.ResourceType.class, resourceType)) {
|
||||
throw new InvalidParameterValueException(
|
||||
String.format("Currently only [%s] can be used to update an extension registration",
|
||||
EnumSet.allOf(ExtensionResourceMap.ResourceType.class)));
|
||||
}
|
||||
ExtensionVO extension = extensionDao.findById(extensionId);
|
||||
if (extension == null) {
|
||||
throw new InvalidParameterValueException("Invalid extension specified");
|
||||
}
|
||||
|
||||
ExtensionResourceMap.ResourceType resType = ExtensionResourceMap.ResourceType.valueOf(resourceType);
|
||||
long resolvedResourceId;
|
||||
if (ExtensionResourceMap.ResourceType.PhysicalNetwork.equals(resType)) {
|
||||
PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findByUuid(resourceId);
|
||||
if (physicalNetwork == null) {
|
||||
try {
|
||||
physicalNetwork = physicalNetworkDao.findById(Long.parseLong(resourceId));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
if (physicalNetwork == null) {
|
||||
throw new InvalidParameterValueException("Invalid physical network ID specified");
|
||||
}
|
||||
resolvedResourceId = physicalNetwork.getId();
|
||||
} else {
|
||||
ClusterVO clusterVO = clusterDao.findByUuid(resourceId);
|
||||
if (clusterVO == null) {
|
||||
throw new InvalidParameterValueException("Invalid cluster ID specified");
|
||||
}
|
||||
resolvedResourceId = clusterVO.getId();
|
||||
}
|
||||
|
||||
List<ExtensionResourceMapVO> mappings = extensionResourceMapDao.listByResourceIdAndType(resolvedResourceId, resType);
|
||||
ExtensionResourceMapVO targetMapping = null;
|
||||
if (CollectionUtils.isNotEmpty(mappings)) {
|
||||
for (ExtensionResourceMapVO mapping : mappings) {
|
||||
if (mapping.getExtensionId() == extensionId) {
|
||||
targetMapping = mapping;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetMapping == null) {
|
||||
throw new InvalidParameterValueException(String.format(
|
||||
"Extension '%s' is not registered with resource %s (%s)",
|
||||
extension.getName(), resourceId, resourceType));
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(cleanupDetails)) {
|
||||
extensionResourceMapDetailsDao.removeDetails(targetMapping.getId());
|
||||
} else {
|
||||
List<ExtensionResourceMapDetailsVO> detailsList = buildExtensionResourceDetailsArray(targetMapping.getId(), details);
|
||||
if (CollectionUtils.isNotEmpty(detailsList)) {
|
||||
appendHiddenExtensionResourceDetails(targetMapping.getId(), detailsList);
|
||||
}
|
||||
detailsList = detailsList.stream()
|
||||
.filter(detail -> detail.getValue() != null)
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(detailsList)) {
|
||||
extensionResourceMapDetailsDao.saveDetails(detailsList);
|
||||
} else {
|
||||
extensionResourceMapDetailsDao.removeDetails(targetMapping.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return extensionDao.findById(extensionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_EXTENSION_RESOURCE_REGISTER, eventDescription = "registering extension resource")
|
||||
public ExtensionResourceMap registerExtensionWithCluster(Cluster cluster, Extension extension,
|
||||
|
|
@ -923,8 +1149,9 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
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()));
|
||||
entry.getKey(), entry.getValue(), display));
|
||||
}
|
||||
extensionResourceMapDetailsDao.saveDetails(detailsVOList);
|
||||
}
|
||||
|
|
@ -934,6 +1161,247 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
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 extensionMap;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = parseServicesFromDetailKeys(extDetails);
|
||||
if (!parsed.isEmpty()) {
|
||||
return parsed;
|
||||
}
|
||||
// Default: the full set of services NetworkExtensionElement supports
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the set of service names from the extension detail map.
|
||||
* From {@code network.services} comma-separated key.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private Set<String> parseServicesFromDetailKeys(Map<String, String> extDetails) {
|
||||
if (extDetails == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
// New format: "network.services" = "SourceNat,StaticNat,..."
|
||||
if (extDetails.containsKey(ExtensionHelper.NETWORK_SERVICES_DETAIL_KEY)) {
|
||||
String value = extDetails.get(ExtensionHelper.NETWORK_SERVICES_DETAIL_KEY);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
Set<String> services = new HashSet<>();
|
||||
for (String s : value.split(",")) {
|
||||
String trimmed = s.trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
services.add(trimmed);
|
||||
}
|
||||
}
|
||||
if (!services.isEmpty()) {
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a full {@code Map<Service, Map<Capability, String>>} from the
|
||||
* extension detail map. From the split keys
|
||||
* {@code network.services} + {@code network.service.capabilities}.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private Map<Service, Map<Capability, String>> buildCapabilitiesFromDetailKeys(
|
||||
Map<String, String> extDetails) {
|
||||
if (extDetails == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
// New split format
|
||||
if (extDetails.containsKey(ExtensionHelper.NETWORK_SERVICES_DETAIL_KEY)) {
|
||||
Set<String> serviceNames = parseServicesFromDetailKeys(extDetails);
|
||||
if (!serviceNames.isEmpty()) {
|
||||
JsonObject capsObj = null;
|
||||
if (extDetails.containsKey(ExtensionHelper.NETWORK_SERVICE_CAPABILITIES_DETAIL_KEY)) {
|
||||
try {
|
||||
capsObj = JsonParser.parseString(
|
||||
extDetails.get(ExtensionHelper.NETWORK_SERVICE_CAPABILITIES_DETAIL_KEY))
|
||||
.getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to parse network.service.capabilities JSON: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
Map<Service, Map<Capability, String>> result = new HashMap<>();
|
||||
for (String svcName : serviceNames) {
|
||||
Service service = Service.getService(svcName);
|
||||
if (service == null) {
|
||||
logger.warn("Unknown network service '{}' in network.services — skipping", svcName);
|
||||
continue;
|
||||
}
|
||||
Map<Capability, String> capMap = new HashMap<>();
|
||||
if (capsObj != null && capsObj.has(svcName)) {
|
||||
JsonObject svcCaps = capsObj.getAsJsonObject(svcName);
|
||||
for (Map.Entry<String, JsonElement> entry : svcCaps.entrySet()) {
|
||||
Capability cap = Capability.getCapability(entry.getKey());
|
||||
if (cap != null) {
|
||||
capMap.put(cap, entry.getValue().getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
result.put(service, capMap);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the boolean service-provided flags on a {@link PhysicalNetworkServiceProviderVO}
|
||||
* based on a set of service names.
|
||||
*/
|
||||
private void applyServicesToNsp(PhysicalNetworkServiceProviderVO nsp, Set<String> services) {
|
||||
nsp.setSourcenatServiceProvided(services.contains("SourceNat"));
|
||||
nsp.setStaticnatServiceProvided(services.contains("StaticNat"));
|
||||
nsp.setPortForwardingServiceProvided(services.contains("PortForwarding"));
|
||||
nsp.setFirewallServiceProvided(services.contains("Firewall"));
|
||||
nsp.setGatewayServiceProvided(services.contains("Gateway"));
|
||||
nsp.setDnsServiceProvided(services.contains("Dns"));
|
||||
nsp.setDhcpServiceProvided(services.contains("Dhcp"));
|
||||
nsp.setUserdataServiceProvided(services.contains("UserData"));
|
||||
nsp.setLbServiceProvided(services.contains("Lb"));
|
||||
nsp.setVpnServiceProvided(services.contains("Vpn"));
|
||||
nsp.setSecuritygroupServiceProvided(services.contains("SecurityGroup"));
|
||||
nsp.setNetworkAclServiceProvided(services.contains("NetworkACL"));
|
||||
}
|
||||
|
||||
/** Keys that are always stored with display=false (sensitive). */
|
||||
private static final Set<String> SENSITIVE_DETAIL_KEYS =
|
||||
Set.of("password", "sshkey");
|
||||
|
||||
/**
|
||||
* Validates that the comma-separated or JSON-array {@code servicesValue} is a
|
||||
* subset of the services declared in the extension's {@code network.services}
|
||||
* Throws {@link InvalidParameterValueException} if any service in the request is not
|
||||
* offered by the extension.
|
||||
*/
|
||||
protected void validateNetworkServicesSubset(Extension extension, String servicesValue) {
|
||||
if (StringUtils.isBlank(servicesValue)) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> extDetails = extensionDetailsDao.listDetailsKeyPairs(extension.getId());
|
||||
Set<String> allowedServices = parseServicesFromDetailKeys(extDetails);
|
||||
if (allowedServices.isEmpty()) {
|
||||
// No services declared → accept any
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the requested services: either comma-separated string or JSON array
|
||||
List<String> requested = parseServicesList(servicesValue);
|
||||
List<String> invalid = requested.stream()
|
||||
.filter(s -> !allowedServices.contains(s))
|
||||
.collect(Collectors.toList());
|
||||
if (!invalid.isEmpty()) {
|
||||
throw new InvalidParameterValueException(String.format(
|
||||
"The following services are not supported by extension '%s': %s. "
|
||||
+ "Supported services are: %s",
|
||||
extension.getName(), invalid, allowedServices));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a services list from either a comma-separated string (e.g.
|
||||
* {@code "SourceNat,StaticNat"}) or a JSON array (e.g.
|
||||
* {@code ["SourceNat","StaticNat"]}).
|
||||
*/
|
||||
private List<String> parseServicesList(String value) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
value = value.trim();
|
||||
if (value.startsWith("[")) {
|
||||
try {
|
||||
JsonArray arr = JsonParser.parseString(value).getAsJsonArray();
|
||||
List<String> result = new ArrayList<>();
|
||||
for (JsonElement el : arr) {
|
||||
result.add(el.getAsString().trim());
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
// fall through to comma-split
|
||||
}
|
||||
}
|
||||
// Comma-separated
|
||||
return Arrays.stream(value.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_EXTENSION_RESOURCE_UNREGISTER, eventDescription = "unregistering extension resource")
|
||||
public Extension unregisterExtensionWithResource(UnregisterExtensionCmd cmd) {
|
||||
|
|
@ -942,10 +1410,15 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
final String resourceType = cmd.getResourceType();
|
||||
if (!EnumUtils.isValidEnum(ExtensionResourceMap.ResourceType.class, resourceType)) {
|
||||
throw new InvalidParameterValueException(
|
||||
String.format("Currently only [%s] can be used to unregister an extension of type Orchestrator",
|
||||
String.format("Currently only [%s] can be used to unregister an extension",
|
||||
EnumSet.allOf(ExtensionResourceMap.ResourceType.class)));
|
||||
}
|
||||
unregisterExtensionWithCluster(resourceId, extensionId);
|
||||
ExtensionResourceMap.ResourceType resType = ExtensionResourceMap.ResourceType.valueOf(resourceType);
|
||||
if (ExtensionResourceMap.ResourceType.PhysicalNetwork.equals(resType)) {
|
||||
unregisterExtensionWithPhysicalNetwork(resourceId, extensionId);
|
||||
} else {
|
||||
unregisterExtensionWithCluster(resourceId, extensionId);
|
||||
}
|
||||
return extensionDao.findById(extensionId);
|
||||
}
|
||||
|
||||
|
|
@ -965,6 +1438,55 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
}
|
||||
}
|
||||
|
||||
protected void unregisterExtensionWithPhysicalNetwork(String resourceId, Long extensionId) {
|
||||
PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findByUuid(resourceId);
|
||||
if (physicalNetwork == null) {
|
||||
try {
|
||||
physicalNetwork = physicalNetworkDao.findById(Long.parseLong(resourceId));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
if (physicalNetwork == null) {
|
||||
throw new InvalidParameterValueException("Invalid physical network ID specified");
|
||||
}
|
||||
// Find the specific map entry for this extension+physical-network combination
|
||||
List<ExtensionResourceMapVO> existingList = extensionResourceMapDao.listByResourceIdAndType(
|
||||
physicalNetwork.getId(), ExtensionResourceMap.ResourceType.PhysicalNetwork);
|
||||
if (existingList == null || existingList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final long physNetId = physicalNetwork.getId();
|
||||
for (ExtensionResourceMapVO existing : existingList) {
|
||||
if (extensionId == null || existing.getExtensionId() == extensionId) {
|
||||
ExtensionVO ext = extensionDao.findById(existing.getExtensionId());
|
||||
if (ext != null) {
|
||||
List<NetworkVO> networksUsingProvider = networkDao.listByPhysicalNetworkAndProvider(
|
||||
physNetId, ext.getName());
|
||||
if (CollectionUtils.isNotEmpty(networksUsingProvider)) {
|
||||
throw new CloudRuntimeException(String.format(
|
||||
"Cannot unregister extension '%s' from physical network %s. "
|
||||
+ "Provider is used by %d existing network(s)",
|
||||
ext.getName(), physNetId, networksUsingProvider.size()));
|
||||
}
|
||||
}
|
||||
|
||||
extensionResourceMapDao.remove(existing.getId());
|
||||
extensionResourceMapDetailsDao.removeDetails(existing.getId());
|
||||
|
||||
// Also remove the auto-created NSP for this extension
|
||||
if (ext != null) {
|
||||
PhysicalNetworkServiceProviderVO nsp =
|
||||
physicalNetworkServiceProviderDao.findByServiceProvider(physNetId, ext.getName());
|
||||
if (nsp != null) {
|
||||
physicalNetworkServiceProviderDao.remove(nsp.getId());
|
||||
logger.info("Removed NetworkServiceProvider '{}' from physical network {} "
|
||||
+ "on extension unregister", ext.getName(), physNetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionResponse createExtensionResponse(Extension extension,
|
||||
EnumSet<ApiConstants.ExtensionDetails> viewDetails) {
|
||||
|
|
@ -988,6 +1510,12 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
Cluster cluster = clusterDao.findById(extensionResourceMapVO.getResourceId());
|
||||
extensionResourceResponse.setId(cluster.getUuid());
|
||||
extensionResourceResponse.setName(cluster.getName());
|
||||
} else if (ExtensionResourceMap.ResourceType.PhysicalNetwork.equals(extensionResourceMapVO.getResourceType())) {
|
||||
PhysicalNetworkVO pn = physicalNetworkDao.findById(extensionResourceMapVO.getResourceId());
|
||||
if (pn != null) {
|
||||
extensionResourceResponse.setId(pn.getUuid());
|
||||
extensionResourceResponse.setName(pn.getName());
|
||||
}
|
||||
}
|
||||
Map<String, String> details = extensionResourceMapDetailsDao.listDetailsKeyPairs(
|
||||
extensionResourceMapVO.getId(), true);
|
||||
|
|
@ -1423,6 +1951,10 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
Pair<Long, Long> clusterAndHostId = virtualMachineManager.findClusterAndHostIdForVm(virtualMachine, false);
|
||||
clusterId = clusterAndHostId.first();
|
||||
hostId = clusterAndHostId.second();
|
||||
} else if (entity instanceof Network) {
|
||||
// Network custom action: dispatched directly to NetworkCustomActionProvider (no agent)
|
||||
Network network = (Network) entity;
|
||||
return runNetworkCustomAction(network, customActionVO, extensionVO, actionResourceType, cmdParameters);
|
||||
}
|
||||
|
||||
if (clusterId == null || hostId == null) {
|
||||
|
|
@ -1499,6 +2031,92 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a custom action for a Network resource by delegating to an
|
||||
* available {@link NetworkCustomActionProvider} (e.g. NetworkExtensionElement).
|
||||
* This path does NOT go through the agent framework.
|
||||
*/
|
||||
protected CustomActionResultResponse runNetworkCustomAction(Network network,
|
||||
ExtensionCustomActionVO customActionVO, ExtensionVO extensionVO,
|
||||
ExtensionCustomAction.ResourceType actionResourceType,
|
||||
Map<String, String> cmdParameters) {
|
||||
|
||||
final String actionName = customActionVO.getName();
|
||||
CustomActionResultResponse response = new CustomActionResultResponse();
|
||||
response.setId(customActionVO.getUuid());
|
||||
response.setName(actionName);
|
||||
response.setObjectName("customactionresult");
|
||||
Map<String, String> result = new HashMap<>();
|
||||
response.setSuccess(false);
|
||||
result.put(ApiConstants.MESSAGE, getActionMessage(false, customActionVO, extensionVO, actionResourceType, network));
|
||||
|
||||
// Resolve action parameters
|
||||
List<ExtensionCustomAction.Parameter> actionParameters = null;
|
||||
Pair<Map<String, String>, Map<String, String>> allDetails =
|
||||
extensionCustomActionDetailsDao.listDetailsKeyPairsWithVisibility(customActionVO.getId());
|
||||
if (allDetails.second().containsKey(ApiConstants.PARAMETERS)) {
|
||||
actionParameters = ExtensionCustomAction.Parameter.toListFromJson(
|
||||
allDetails.second().get(ApiConstants.PARAMETERS));
|
||||
}
|
||||
Map<String, Object> parameters = null;
|
||||
if (CollectionUtils.isNotEmpty(actionParameters)) {
|
||||
parameters = ExtensionCustomAction.Parameter.validateParameterValues(actionParameters, cmdParameters);
|
||||
}
|
||||
|
||||
// Find the provider name for this network (try each service until we find one)
|
||||
String providerName = null;
|
||||
for (Service service : new Service[]{Service.SourceNat, Service.StaticNat,
|
||||
Service.PortForwarding, Service.Firewall, Service.Gateway}) {
|
||||
providerName = networkServiceMapDao.getProviderForServiceInNetwork(network.getId(), service);
|
||||
if (StringUtils.isNotBlank(providerName)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (StringUtils.isBlank(providerName)) {
|
||||
logger.error("No network service provider found for network {}", network.getId());
|
||||
result.put(ApiConstants.DETAILS, "No network service provider found for this network");
|
||||
response.setResult(result);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Get the network element implementing that provider
|
||||
NetworkElement element = networkModel.getElementImplementingProvider(providerName);
|
||||
if (element == null) {
|
||||
logger.error("No NetworkElement found implementing provider '{}' for network {}", providerName, network.getId());
|
||||
result.put(ApiConstants.DETAILS, "No network element found for provider: " + providerName);
|
||||
response.setResult(result);
|
||||
return response;
|
||||
}
|
||||
|
||||
// The element must implement NetworkCustomActionProvider
|
||||
if (!(element instanceof NetworkCustomActionProvider)) {
|
||||
logger.error("Network element '{}' for provider '{}' does not support custom actions",
|
||||
element.getClass().getSimpleName(), providerName);
|
||||
result.put(ApiConstants.DETAILS, "Provider '" + providerName + "' does not support custom actions");
|
||||
response.setResult(result);
|
||||
return response;
|
||||
}
|
||||
|
||||
NetworkCustomActionProvider provider = (NetworkCustomActionProvider) element;
|
||||
try {
|
||||
if (!provider.canHandleCustomAction(network)) {
|
||||
throw new CloudRuntimeException("Provider '" + providerName + "' cannot handle custom action for this network");
|
||||
}
|
||||
logger.info("Running network custom action '{}' on network {} via {} (provider: {})",
|
||||
actionName, network.getId(), element.getClass().getSimpleName(), providerName);
|
||||
String output = provider.runCustomAction(network, actionName, parameters);
|
||||
boolean success = output != null;
|
||||
response.setSuccess(success);
|
||||
result.put(ApiConstants.MESSAGE, getActionMessage(success, customActionVO, extensionVO, actionResourceType, network));
|
||||
result.put(ApiConstants.DETAILS, success ? output : "Action failed — check management server logs for details");
|
||||
} catch (Exception e) {
|
||||
logger.error("Network custom action '{}' threw exception: {}", actionName, e.getMessage(), e);
|
||||
result.put(ApiConstants.DETAILS, "Action failed: " + e.getMessage());
|
||||
}
|
||||
response.setResult(result);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionCustomActionResponse createCustomActionResponse(ExtensionCustomAction customAction) {
|
||||
ExtensionCustomActionResponse response = new ExtensionCustomActionResponse(customAction.getUuid(),
|
||||
|
|
@ -1608,11 +2226,8 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
if (MapUtils.isEmpty(details)) {
|
||||
return;
|
||||
}
|
||||
List<ExtensionResourceMapDetailsVO> detailsList = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : details.entrySet()) {
|
||||
detailsList.add(new ExtensionResourceMapDetailsVO(extensionResourceMapId, entry.getKey(),
|
||||
entry.getValue()));
|
||||
}
|
||||
List<ExtensionResourceMapDetailsVO> detailsList =
|
||||
buildExtensionResourceDetailsArray(extensionResourceMapId, details);
|
||||
extensionResourceMapDetailsDao.saveDetails(detailsList);
|
||||
}
|
||||
|
||||
|
|
@ -1677,6 +2292,26 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
return reservedDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getExtensionIdForPhysicalNetwork(long physicalNetworkId) {
|
||||
// Returns the first (primary) extension for backward compatibility
|
||||
List<ExtensionResourceMapVO> maps = extensionResourceMapDao.listByResourceIdAndType(physicalNetworkId,
|
||||
ExtensionResourceMap.ResourceType.PhysicalNetwork);
|
||||
if (maps == null || maps.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return maps.get(0).getExtensionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Extension getExtensionForPhysicalNetwork(long physicalNetworkId) {
|
||||
Long extensionId = getExtensionIdForPhysicalNetwork(physicalNetworkId);
|
||||
if (extensionId == null) {
|
||||
return null;
|
||||
}
|
||||
return extensionDao.findById(extensionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
long pathStateCheckInterval = PathStateCheckInterval.value();
|
||||
|
|
@ -1714,6 +2349,7 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
cmds.add(UpdateExtensionCmd.class);
|
||||
cmds.add(RegisterExtensionCmd.class);
|
||||
cmds.add(UnregisterExtensionCmd.class);
|
||||
cmds.add(UpdateRegisteredExtensionCmd.class);
|
||||
return cmds;
|
||||
}
|
||||
|
||||
|
|
@ -1765,4 +2401,106 @@ public class ExtensionsManagerImpl extends ManagerBase implements ExtensionsMana
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtensionScriptPath(Extension extension) {
|
||||
if (extension == null) {
|
||||
return null;
|
||||
}
|
||||
return externalProvisioner.getExtensionPath(extension.getRelativePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getExtensionDetails(long extensionId) {
|
||||
return extensionDetailsDao.listDetailsKeyPairs(extensionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Extension getExtensionForPhysicalNetworkAndProvider(long physicalNetworkId, String providerName) {
|
||||
if (StringUtils.isBlank(providerName)) {
|
||||
return null;
|
||||
}
|
||||
List<ExtensionResourceMapVO> maps = extensionResourceMapDao.listByResourceIdAndType(
|
||||
physicalNetworkId, ExtensionResourceMap.ResourceType.PhysicalNetwork);
|
||||
if (maps == null || maps.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (ExtensionResourceMapVO map : maps) {
|
||||
ExtensionVO ext = extensionDao.findById(map.getExtensionId());
|
||||
if (ext != null && providerName.equalsIgnoreCase(ext.getName())) {
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAllResourceMapDetailsForExtensionOnPhysicalNetwork(long physicalNetworkId, long extensionId) {
|
||||
List<ExtensionResourceMapVO> maps = extensionResourceMapDao.listByResourceIdAndType(
|
||||
physicalNetworkId, ExtensionResourceMap.ResourceType.PhysicalNetwork);
|
||||
if (maps == null || maps.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
for (ExtensionResourceMapVO map : maps) {
|
||||
if (map.getExtensionId() == extensionId) {
|
||||
Map<String, String> details = extensionResourceMapDetailsDao.listDetailsKeyPairs(map.getId());
|
||||
return details != null ? details : new HashMap<>();
|
||||
}
|
||||
}
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNetworkExtensionProvider(String providerName) {
|
||||
if (StringUtils.isBlank(providerName)) {
|
||||
return false;
|
||||
}
|
||||
List<ExtensionVO> networkOrchExtensions = extensionDao.listByType(Extension.Type.NetworkOrchestrator);
|
||||
if (networkOrchExtensions == null || networkOrchExtensions.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return networkOrchExtensions.stream()
|
||||
.anyMatch(ext -> providerName.equalsIgnoreCase(ext.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Extension> listExtensionsByType(Extension.Type type) {
|
||||
if (type == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<ExtensionVO> extensions = extensionDao.listByType(type);
|
||||
if (extensions == null || extensions.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return new ArrayList<>(extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Service, Map<Capability, String>> getNetworkCapabilitiesForProvider(Long physicalNetworkId,
|
||||
String providerName) {
|
||||
if (StringUtils.isBlank(providerName)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
Extension extension = null;
|
||||
if (physicalNetworkId != null) {
|
||||
extension = getExtensionForPhysicalNetworkAndProvider(physicalNetworkId, providerName);
|
||||
}
|
||||
if (extension == null) {
|
||||
// Search across all physical networks
|
||||
List<ExtensionVO> networkOrchExtensions = extensionDao.listByType(Extension.Type.NetworkOrchestrator);
|
||||
if (networkOrchExtensions != null) {
|
||||
for (ExtensionVO ext : networkOrchExtensions) {
|
||||
if (providerName.equalsIgnoreCase(ext.getName())) {
|
||||
extension = ext;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extension == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
Map<String, String> extDetails = extensionDetailsDao.listDetailsKeyPairs(extension.getId());
|
||||
return buildCapabilitiesFromDetailKeys(extDetails);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -40,7 +40,7 @@ public class ExtensionDetailsVO implements ResourceDetail {
|
|||
@Column(name = "name", nullable = false, length = 255)
|
||||
private String name;
|
||||
|
||||
@Column(name = "value", nullable = false, length = 255)
|
||||
@Column(name = "value", nullable = false, length = 4096)
|
||||
private String value;
|
||||
|
||||
@Column(name = "display")
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class ExtensionResourceMapDetailsVO implements ResourceDetail {
|
|||
@Column(name = "name", nullable = false, length = 255)
|
||||
private String name;
|
||||
|
||||
@Column(name = "value", nullable = false, length = 255)
|
||||
@Column(name = "value", nullable = false, length = 4096)
|
||||
private String value;
|
||||
|
||||
@Column(name = "display")
|
||||
|
|
|
|||
|
|
@ -33,4 +33,7 @@
|
|||
<bean id="ExtensionsManager" class="org.apache.cloudstack.framework.extensions.manager.ExtensionsManagerImpl" />
|
||||
<bean id="ExtensionCustomActionDaoImpl" class="org.apache.cloudstack.framework.extensions.dao.ExtensionCustomActionDaoImpl" />
|
||||
<bean id="ExtensionCustomActionDetailsDaoImpl" class="org.apache.cloudstack.framework.extensions.dao.ExtensionCustomActionDetailsDaoImpl" />
|
||||
<bean id="networkExtension" class="org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement">
|
||||
<property name="name" value="NetworkExtension" />
|
||||
</bean>
|
||||
</beans>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.framework.extensions.api;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.ExtensionResponse;
|
||||
import org.apache.cloudstack.extension.Extension;
|
||||
import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
public class UpdateRegisteredExtensionCmdTest {
|
||||
|
||||
private UpdateRegisteredExtensionCmd cmd;
|
||||
private ExtensionsManager extensionsManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
cmd = Mockito.spy(new UpdateRegisteredExtensionCmd());
|
||||
extensionsManager = mock(ExtensionsManager.class);
|
||||
ReflectionTestUtils.setField(cmd, "extensionsManager", extensionsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extensionIdReturnsNullWhenUnset() {
|
||||
ReflectionTestUtils.setField(cmd, "extensionId", null);
|
||||
assertNull(cmd.getExtensionId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resourceIdReturnsValueWhenSet() {
|
||||
String resourceId = "resource-123";
|
||||
ReflectionTestUtils.setField(cmd, "resourceId", resourceId);
|
||||
assertEquals(resourceId, cmd.getResourceId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resourceTypeReturnsValueWhenSet() {
|
||||
String resourceType = "PhysicalNetwork";
|
||||
ReflectionTestUtils.setField(cmd, "resourceType", resourceType);
|
||||
assertEquals(resourceType, cmd.getResourceType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detailsReturnsEmptyMapWhenUnset() {
|
||||
ReflectionTestUtils.setField(cmd, "details", null);
|
||||
Map<String, String> details = cmd.getDetails();
|
||||
assertNotNull(details);
|
||||
assertTrue(details.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeSetsExtensionResponseWhenManagerSucceeds() {
|
||||
Extension extension = mock(Extension.class);
|
||||
ExtensionResponse response = mock(ExtensionResponse.class);
|
||||
when(extensionsManager.updateRegisteredExtensionWithResource(cmd)).thenReturn(extension);
|
||||
when(extensionsManager.createExtensionResponse(extension, EnumSet.of(ApiConstants.ExtensionDetails.all)))
|
||||
.thenReturn(response);
|
||||
|
||||
doNothing().when(cmd).setResponseObject(any());
|
||||
|
||||
cmd.execute();
|
||||
|
||||
verify(extensionsManager).updateRegisteredExtensionWithResource(cmd);
|
||||
verify(extensionsManager).createExtensionResponse(extension, EnumSet.of(ApiConstants.ExtensionDetails.all));
|
||||
verify(cmd).setResponseObject(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeThrowsServerApiExceptionWhenManagerFails() {
|
||||
when(extensionsManager.updateRegisteredExtensionWithResource(cmd))
|
||||
.thenThrow(new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update registered extension"));
|
||||
|
||||
try {
|
||||
cmd.execute();
|
||||
fail("Expected ServerApiException");
|
||||
} catch (ServerApiException e) {
|
||||
assertEquals("Failed to update registered extension", e.getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,6 +37,7 @@ import static org.mockito.Mockito.eq;
|
|||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
|
@ -74,6 +75,7 @@ import org.apache.cloudstack.framework.extensions.api.RunCustomActionCmd;
|
|||
import org.apache.cloudstack.framework.extensions.api.UnregisterExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateCustomActionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateRegisteredExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.command.CleanupExtensionFilesCommand;
|
||||
import org.apache.cloudstack.framework.extensions.command.ExtensionServerActionBaseCommand;
|
||||
import org.apache.cloudstack.framework.extensions.command.GetExtensionPathChecksumCommand;
|
||||
|
|
@ -87,6 +89,7 @@ import org.apache.cloudstack.framework.extensions.dao.ExtensionResourceMapDetail
|
|||
import org.apache.cloudstack.framework.extensions.vo.ExtensionCustomActionDetailsVO;
|
||||
import org.apache.cloudstack.framework.extensions.vo.ExtensionCustomActionVO;
|
||||
import org.apache.cloudstack.framework.extensions.vo.ExtensionDetailsVO;
|
||||
import org.apache.cloudstack.framework.extensions.vo.ExtensionResourceMapDetailsVO;
|
||||
import org.apache.cloudstack.framework.extensions.vo.ExtensionResourceMapVO;
|
||||
import org.apache.cloudstack.framework.extensions.vo.ExtensionVO;
|
||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
||||
|
|
@ -94,6 +97,7 @@ import org.apache.commons.collections.CollectionUtils;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
|
|
@ -122,6 +126,15 @@ import com.cloud.host.dao.HostDao;
|
|||
import com.cloud.host.dao.HostDetailsDao;
|
||||
import com.cloud.hypervisor.ExternalProvisioner;
|
||||
import com.cloud.hypervisor.Hypervisor;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.NetworkModel;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
import com.cloud.network.dao.NetworkServiceMapDao;
|
||||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.network.dao.PhysicalNetworkDao;
|
||||
import com.cloud.network.dao.PhysicalNetworkVO;
|
||||
import com.cloud.network.element.NetworkElement;
|
||||
import org.apache.cloudstack.extension.NetworkCustomActionProvider;
|
||||
import com.cloud.org.Cluster;
|
||||
import com.cloud.serializer.GsonHelper;
|
||||
import com.cloud.storage.dao.VMTemplateDao;
|
||||
|
|
@ -138,7 +151,7 @@ import com.cloud.vm.VirtualMachineManager;
|
|||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||
public class ExtensionsManagerImplTest {
|
||||
|
||||
@Spy
|
||||
|
|
@ -185,6 +198,14 @@ public class ExtensionsManagerImplTest {
|
|||
private RoleService roleService;
|
||||
@Mock
|
||||
private AccountService accountService;
|
||||
@Mock
|
||||
private PhysicalNetworkDao physicalNetworkDao;
|
||||
@Mock
|
||||
private NetworkDao networkDao;
|
||||
@Mock
|
||||
private NetworkServiceMapDao networkServiceMapDao;
|
||||
@Mock
|
||||
private NetworkModel networkModel;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
|
@ -290,14 +311,6 @@ public class ExtensionsManagerImplTest {
|
|||
assertNull(extensionsManager.getExtensionFromResource(ExtensionCustomAction.ResourceType.VirtualMachine, "uuid"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getActionMessageReturnsDefaultOnBlank() {
|
||||
ExtensionCustomAction action = mock(ExtensionCustomAction.class);
|
||||
Extension ext = mock(Extension.class);
|
||||
when(action.getSuccessMessage()).thenReturn(null);
|
||||
String msg = extensionsManager.getActionMessage(true, action, ext, ExtensionCustomAction.ResourceType.VirtualMachine, null);
|
||||
assertTrue(msg.contains("Successfully completed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getActionMessageReturnsDefaultMessageForSuccessWithoutCustomMessage() {
|
||||
|
|
@ -343,14 +356,6 @@ public class ExtensionsManagerImplTest {
|
|||
assertEquals("Custom failure message", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getActionMessageHandlesNullActionMessage() {
|
||||
ExtensionCustomAction action = mock(ExtensionCustomAction.class);
|
||||
when(action.getSuccessMessage()).thenReturn(null);
|
||||
Extension extension = mock(Extension.class);
|
||||
String result = extensionsManager.getActionMessage(true, action, extension, ExtensionCustomAction.ResourceType.VirtualMachine, null);
|
||||
assertTrue(result.contains("Successfully completed"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFilteredExternalDetailsReturnsFilteredMap() {
|
||||
|
|
@ -381,26 +386,6 @@ public class ExtensionsManagerImplTest {
|
|||
anyLong(), anyLong(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateExtensionPathReadyUpdatesWhenStateDiffers() {
|
||||
Extension ext = mock(Extension.class);
|
||||
when(ext.getId()).thenReturn(1L);
|
||||
when(ext.isPathReady()).thenReturn(false);
|
||||
ExtensionVO vo = mock(ExtensionVO.class);
|
||||
when(extensionDao.createForUpdate(1L)).thenReturn(vo);
|
||||
when(extensionDao.update(1L, vo)).thenReturn(true);
|
||||
extensionsManager.updateExtensionPathReady(ext, true);
|
||||
verify(extensionDao).update(1L, vo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableExtensionUpdatesState() {
|
||||
ExtensionVO vo = mock(ExtensionVO.class);
|
||||
when(extensionDao.createForUpdate(1L)).thenReturn(vo);
|
||||
when(extensionDao.update(1L, vo)).thenReturn(true);
|
||||
extensionsManager.disableExtension(1L);
|
||||
verify(extensionDao).update(1L, vo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtensionFromResourceReturnsExtensionForValidResource() {
|
||||
|
|
@ -558,41 +543,29 @@ public class ExtensionsManagerImplTest {
|
|||
assertEquals("/tmp/extensions", extensionsManager.getExtensionsPath());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getExtensionIdForClusterReturnsNullIfNoMap() {
|
||||
when(extensionResourceMapDao.findByResourceIdAndType(anyLong(), any())).thenReturn(null);
|
||||
assertNull(extensionsManager.getExtensionIdForCluster(1L));
|
||||
public void checkExtensionPathSyncUpdatesReadyWhenStateDiffers() {
|
||||
Extension ext = mock(Extension.class);
|
||||
when(ext.getName()).thenReturn("ext");
|
||||
when(ext.getRelativePath()).thenReturn("entry.sh");
|
||||
when(ext.isPathReady()).thenReturn(false);
|
||||
ExtensionVO vo = mock(ExtensionVO.class);
|
||||
when(extensionDao.createForUpdate(1L)).thenReturn(vo);
|
||||
when(extensionDao.update(1L, vo)).thenReturn(true);
|
||||
extensionsManager.checkExtensionPathState(ext, Collections.emptyList());
|
||||
verify(extensionsManager).updateExtensionPathReady(ext, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtensionIdForClusterReturnsIdIfMapExists() {
|
||||
ExtensionResourceMapVO map = mock(ExtensionResourceMapVO.class);
|
||||
when(map.getExtensionId()).thenReturn(5L);
|
||||
when(extensionResourceMapDao.findByResourceIdAndType(anyLong(), any())).thenReturn(map);
|
||||
assertEquals(Long.valueOf(5L), extensionsManager.getExtensionIdForCluster(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtensionReturnsExtension() {
|
||||
ExtensionVO ext = mock(ExtensionVO.class);
|
||||
when(extensionDao.findById(1L)).thenReturn(ext);
|
||||
assertEquals(ext, extensionsManager.getExtension(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtensionForClusterReturnsNullIfNoId() {
|
||||
when(extensionResourceMapDao.findByResourceIdAndType(anyLong(), any())).thenReturn(null);
|
||||
assertNull(extensionsManager.getExtensionForCluster(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtensionForClusterReturnsExtensionIfIdExists() {
|
||||
ExtensionResourceMapVO map = mock(ExtensionResourceMapVO.class);
|
||||
when(map.getExtensionId()).thenReturn(5L);
|
||||
when(extensionResourceMapDao.findByResourceIdAndType(anyLong(), any())).thenReturn(map);
|
||||
ExtensionVO ext = mock(ExtensionVO.class);
|
||||
when(extensionDao.findById(5L)).thenReturn(ext);
|
||||
assertEquals(ext, extensionsManager.getExtensionForCluster(1L));
|
||||
public void checkExtensionPathSyncUpdatesReadyWhenStateUnchanged() {
|
||||
Extension ext = mock(Extension.class);
|
||||
when(ext.getName()).thenReturn("ext");
|
||||
when(ext.getRelativePath()).thenReturn("entry.sh");
|
||||
when(ext.isPathReady()).thenReturn(true);
|
||||
when(externalProvisioner.getChecksumForExtensionPath("ext", "entry.sh")).thenReturn("checksum123");
|
||||
extensionsManager.checkExtensionPathState(ext, Collections.emptyList());
|
||||
verify(extensionsManager, times(1)).updateExtensionPathReady(any(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -1024,13 +997,6 @@ public class ExtensionsManagerImplTest {
|
|||
verify(extensionDao).remove(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterExtensionWithResource_InvalidResourceType() {
|
||||
RegisterExtensionCmd cmd = mock(RegisterExtensionCmd.class);
|
||||
when(cmd.getResourceType()).thenReturn("InvalidType");
|
||||
|
||||
assertThrows(InvalidParameterValueException.class, () -> extensionsManager.registerExtensionWithResource(cmd));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerExtensionWithResourceRegistersSuccessfullyForValidResourceType() {
|
||||
|
|
@ -1063,8 +1029,6 @@ public class ExtensionsManagerImplTest {
|
|||
RegisterExtensionCmd cmd = mock(RegisterExtensionCmd.class);
|
||||
when(cmd.getResourceType()).thenReturn(ExtensionResourceMap.ResourceType.Cluster.name());
|
||||
when(cmd.getResourceId()).thenReturn(UUID.randomUUID().toString());
|
||||
ClusterVO clusterVO = mock(ClusterVO.class);
|
||||
when(clusterDao.findByUuid(anyString())).thenReturn(clusterVO);
|
||||
extensionsManager.registerExtensionWithResource(cmd);
|
||||
}
|
||||
|
||||
|
|
@ -1139,6 +1103,159 @@ public class ExtensionsManagerImplTest {
|
|||
verify(extensionResourceMapDao, never()).remove(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unregisterExtensionWithResourceThrowsWhenProviderUsedByExistingNetworks() {
|
||||
UnregisterExtensionCmd cmd = mock(UnregisterExtensionCmd.class);
|
||||
when(cmd.getResourceType()).thenReturn(ExtensionResourceMap.ResourceType.PhysicalNetwork.name());
|
||||
when(cmd.getResourceId()).thenReturn("physnet-uuid");
|
||||
when(cmd.getExtensionId()).thenReturn(1L);
|
||||
|
||||
PhysicalNetworkVO physicalNetwork = mock(PhysicalNetworkVO.class);
|
||||
when(physicalNetwork.getId()).thenReturn(42L);
|
||||
when(physicalNetworkDao.findByUuid("physnet-uuid")).thenReturn(physicalNetwork);
|
||||
|
||||
ExtensionResourceMapVO existing = mock(ExtensionResourceMapVO.class);
|
||||
when(existing.getExtensionId()).thenReturn(1L);
|
||||
when(extensionResourceMapDao.listByResourceIdAndType(42L, ExtensionResourceMap.ResourceType.PhysicalNetwork))
|
||||
.thenReturn(List.of(existing));
|
||||
|
||||
ExtensionVO extension = mock(ExtensionVO.class);
|
||||
when(extension.getName()).thenReturn("extnet-provider");
|
||||
when(extensionDao.findById(1L)).thenReturn(extension);
|
||||
|
||||
NetworkVO network = mock(NetworkVO.class);
|
||||
when(networkDao.listByPhysicalNetworkAndProvider(42L, "extnet-provider")).thenReturn(List.of(network));
|
||||
|
||||
assertThrows(CloudRuntimeException.class, () -> extensionsManager.unregisterExtensionWithResource(cmd));
|
||||
verify(extensionResourceMapDao, never()).remove(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRegisteredExtensionWithResourceUpdatesDetailsForExistingMapping() {
|
||||
UpdateRegisteredExtensionCmd cmd = mock(UpdateRegisteredExtensionCmd.class);
|
||||
when(cmd.getResourceType()).thenReturn(ExtensionResourceMap.ResourceType.PhysicalNetwork.name());
|
||||
when(cmd.getResourceId()).thenReturn("physnet-uuid");
|
||||
when(cmd.getExtensionId()).thenReturn(1L);
|
||||
when(cmd.getDetails()).thenReturn(Map.of("username", "root", "hosts", "10.10.10.10"));
|
||||
when(cmd.isCleanupDetails()).thenReturn(false);
|
||||
|
||||
ExtensionVO extension = mock(ExtensionVO.class);
|
||||
when(extension.getName()).thenReturn("extnet-provider");
|
||||
when(extensionDao.findById(1L)).thenReturn(extension);
|
||||
|
||||
PhysicalNetworkVO physicalNetwork = mock(PhysicalNetworkVO.class);
|
||||
when(physicalNetwork.getId()).thenReturn(42L);
|
||||
when(physicalNetworkDao.findByUuid("physnet-uuid")).thenReturn(physicalNetwork);
|
||||
|
||||
ExtensionResourceMapVO existing = mock(ExtensionResourceMapVO.class);
|
||||
when(existing.getExtensionId()).thenReturn(1L);
|
||||
when(existing.getId()).thenReturn(100L);
|
||||
when(extensionResourceMapDao.listByResourceIdAndType(42L, ExtensionResourceMap.ResourceType.PhysicalNetwork))
|
||||
.thenReturn(List.of(existing));
|
||||
|
||||
Extension result = extensionsManager.updateRegisteredExtensionWithResource(cmd);
|
||||
|
||||
assertEquals(extension, result);
|
||||
verify(extensionResourceMapDetailsDao, never()).removeDetails(anyLong());
|
||||
verify(extensionResourceMapDetailsDao).saveDetails(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRegisteredExtensionWithResourceCleanupDetailsFirstThenSaveRequested() {
|
||||
UpdateRegisteredExtensionCmd cmd = mock(UpdateRegisteredExtensionCmd.class);
|
||||
when(cmd.getResourceType()).thenReturn(ExtensionResourceMap.ResourceType.PhysicalNetwork.name());
|
||||
when(cmd.getResourceId()).thenReturn("physnet-uuid");
|
||||
when(cmd.getExtensionId()).thenReturn(1L);
|
||||
when(cmd.getDetails()).thenReturn(Map.of("username", "root", "password", "secret"));
|
||||
when(cmd.isCleanupDetails()).thenReturn(true);
|
||||
|
||||
ExtensionVO extension = mock(ExtensionVO.class);
|
||||
when(extensionDao.findById(1L)).thenReturn(extension);
|
||||
|
||||
PhysicalNetworkVO physicalNetwork = mock(PhysicalNetworkVO.class);
|
||||
when(physicalNetwork.getId()).thenReturn(42L);
|
||||
when(physicalNetworkDao.findByUuid("physnet-uuid")).thenReturn(physicalNetwork);
|
||||
|
||||
ExtensionResourceMapVO existing = mock(ExtensionResourceMapVO.class);
|
||||
when(existing.getExtensionId()).thenReturn(1L);
|
||||
when(existing.getId()).thenReturn(100L);
|
||||
when(extensionResourceMapDao.listByResourceIdAndType(42L, ExtensionResourceMap.ResourceType.PhysicalNetwork))
|
||||
.thenReturn(List.of(existing));
|
||||
|
||||
extensionsManager.updateRegisteredExtensionWithResource(cmd);
|
||||
|
||||
verify(extensionResourceMapDetailsDao).removeDetails(100L);
|
||||
verify(extensionResourceMapDetailsDao, never()).saveDetails(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void updateRegisteredExtensionWithResourceStoresSensitiveDetailsWithDisplayFalse() {
|
||||
UpdateRegisteredExtensionCmd cmd = mock(UpdateRegisteredExtensionCmd.class);
|
||||
when(cmd.getResourceType()).thenReturn(ExtensionResourceMap.ResourceType.PhysicalNetwork.name());
|
||||
when(cmd.getResourceId()).thenReturn("physnet-uuid");
|
||||
when(cmd.getExtensionId()).thenReturn(1L);
|
||||
when(cmd.getDetails()).thenReturn(Map.of("username", "root", "password", "newSecret"));
|
||||
when(cmd.isCleanupDetails()).thenReturn(false);
|
||||
|
||||
ExtensionVO extension = mock(ExtensionVO.class);
|
||||
when(extensionDao.findById(1L)).thenReturn(extension);
|
||||
|
||||
PhysicalNetworkVO physicalNetwork = mock(PhysicalNetworkVO.class);
|
||||
when(physicalNetwork.getId()).thenReturn(42L);
|
||||
when(physicalNetworkDao.findByUuid("physnet-uuid")).thenReturn(physicalNetwork);
|
||||
|
||||
ExtensionResourceMapVO existing = mock(ExtensionResourceMapVO.class);
|
||||
when(existing.getExtensionId()).thenReturn(1L);
|
||||
when(existing.getId()).thenReturn(100L);
|
||||
when(extensionResourceMapDao.listByResourceIdAndType(42L, ExtensionResourceMap.ResourceType.PhysicalNetwork))
|
||||
.thenReturn(List.of(existing));
|
||||
extensionsManager.updateRegisteredExtensionWithResource(cmd);
|
||||
|
||||
ArgumentCaptor<List<ExtensionResourceMapDetailsVO>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(extensionResourceMapDetailsDao).saveDetails(captor.capture());
|
||||
verify(extensionResourceMapDetailsDao, never()).removeDetails(anyLong());
|
||||
List<ExtensionResourceMapDetailsVO> savedDetails = captor.getValue();
|
||||
|
||||
ExtensionResourceMapDetailsVO passwordDetail = savedDetails.stream()
|
||||
.filter(detail -> "password".equals(detail.getName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assertNotNull(passwordDetail);
|
||||
assertFalse(passwordDetail.isDisplay());
|
||||
assertEquals("newSecret", passwordDetail.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void registerExtensionWithClusterStoresSensitiveDetailsWithDisplayFalse() {
|
||||
Cluster cluster = mock(Cluster.class);
|
||||
when(cluster.getId()).thenReturn(12L);
|
||||
when(cluster.getName()).thenReturn("cluster-12");
|
||||
when(cluster.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.External);
|
||||
|
||||
Extension extension = mock(Extension.class);
|
||||
when(extension.getId()).thenReturn(5L);
|
||||
|
||||
ExtensionResourceMapVO persistedMap = mock(ExtensionResourceMapVO.class);
|
||||
when(persistedMap.getId()).thenReturn(120L);
|
||||
when(extensionResourceMapDao.persist(any())).thenReturn(persistedMap);
|
||||
|
||||
extensionsManager.registerExtensionWithCluster(cluster, extension,
|
||||
Map.of("username", "admin", "password", "s3cr3t"));
|
||||
|
||||
ArgumentCaptor<List<ExtensionResourceMapDetailsVO>> captor = ArgumentCaptor.forClass(List.class);
|
||||
verify(extensionResourceMapDetailsDao).saveDetails(captor.capture());
|
||||
List<ExtensionResourceMapDetailsVO> savedDetails = captor.getValue();
|
||||
|
||||
ExtensionResourceMapDetailsVO passwordDetail = savedDetails.stream()
|
||||
.filter(detail -> "password".equals(detail.getName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assertNotNull(passwordDetail);
|
||||
assertFalse(passwordDetail.isDisplay());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateExtensionResponse_BasicFields() {
|
||||
Extension extension = mock(Extension.class);
|
||||
|
|
@ -2218,4 +2335,142 @@ public class ExtensionsManagerImplTest {
|
|||
assertEquals(reservedResourceDetails.size(), entry.getValue().size());
|
||||
assertTrue(reservedResourceDetails.containsAll(entry.getValue()));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tests for ExtensionHelper methods (external network device support)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
public void getExtensionForPhysicalNetworkReturnsExtensionWhenRegistered() {
|
||||
long physNetId = 10L;
|
||||
long extensionId = 5L;
|
||||
ExtensionResourceMapVO mapVO = mock(ExtensionResourceMapVO.class);
|
||||
when(mapVO.getExtensionId()).thenReturn(extensionId);
|
||||
when(extensionResourceMapDao.listByResourceIdAndType(physNetId,
|
||||
ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(List.of(mapVO));
|
||||
ExtensionVO ext = mock(ExtensionVO.class);
|
||||
when(extensionDao.findById(extensionId)).thenReturn(ext);
|
||||
|
||||
Extension result = extensionsManager.getExtensionForPhysicalNetwork(physNetId);
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(ext, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExtensionForPhysicalNetworkReturnsNullWhenNotRegistered() {
|
||||
long physNetId = 10L;
|
||||
when(extensionResourceMapDao.listByResourceIdAndType(physNetId,
|
||||
ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(Collections.emptyList());
|
||||
|
||||
Extension result = extensionsManager.getExtensionForPhysicalNetwork(physNetId);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
|
||||
// Helper: a mock object that is both a NetworkElement and a NetworkCustomActionProvider
|
||||
interface MockNetworkElement extends NetworkElement, NetworkCustomActionProvider {}
|
||||
|
||||
@Test
|
||||
public void runNetworkCustomActionSucceeds() {
|
||||
Network network = mock(Network.class);
|
||||
when(network.getId()).thenReturn(5L);
|
||||
when(network.getName()).thenReturn("test-net");
|
||||
|
||||
ExtensionCustomActionVO actionVO = mock(ExtensionCustomActionVO.class);
|
||||
when(actionVO.getUuid()).thenReturn("action-uuid");
|
||||
when(actionVO.getName()).thenReturn("reboot-device");
|
||||
when(actionVO.getId()).thenReturn(1L);
|
||||
when(actionVO.getSuccessMessage()).thenReturn(null);
|
||||
when(actionVO.getErrorMessage()).thenReturn(null);
|
||||
|
||||
ExtensionVO extensionVO = mock(ExtensionVO.class);
|
||||
when(extensionVO.getName()).thenReturn("my-extnet");
|
||||
|
||||
Pair<Map<String, String>, Map<String, String>> details = new Pair<>(new HashMap<>(), new HashMap<>());
|
||||
when(extensionCustomActionDetailsDao.listDetailsKeyPairsWithVisibility(1L)).thenReturn(details);
|
||||
|
||||
// networkServiceMapDao returns provider name for SourceNat
|
||||
when(networkServiceMapDao.getProviderForServiceInNetwork(eq(5L), any())).thenReturn("my-extnet");
|
||||
|
||||
// element implements both NetworkElement and NetworkCustomActionProvider
|
||||
MockNetworkElement element = mock(MockNetworkElement.class);
|
||||
when(element.canHandleCustomAction(eq(network))).thenReturn(true);
|
||||
when(element.runCustomAction(eq(network), eq("reboot-device"), any())).thenReturn("OK: bridge bounced");
|
||||
when(networkModel.getElementImplementingProvider("my-extnet")).thenReturn(element);
|
||||
|
||||
CustomActionResultResponse resp = extensionsManager.runNetworkCustomAction(
|
||||
network, actionVO, extensionVO,
|
||||
ExtensionCustomAction.ResourceType.Network, new HashMap<>());
|
||||
|
||||
assertTrue(resp.isSuccess());
|
||||
assertEquals("OK: bridge bounced", resp.getResult().get(ApiConstants.DETAILS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runNetworkCustomActionFailsWhenNoProvider() {
|
||||
Network network = mock(Network.class);
|
||||
when(network.getId()).thenReturn(5L);
|
||||
when(network.getName()).thenReturn("test-net");
|
||||
|
||||
ExtensionCustomActionVO actionVO = mock(ExtensionCustomActionVO.class);
|
||||
when(actionVO.getUuid()).thenReturn("action-uuid");
|
||||
when(actionVO.getName()).thenReturn("dump-config");
|
||||
when(actionVO.getId()).thenReturn(2L);
|
||||
when(actionVO.getSuccessMessage()).thenReturn(null);
|
||||
when(actionVO.getErrorMessage()).thenReturn(null);
|
||||
|
||||
ExtensionVO extensionVO = mock(ExtensionVO.class);
|
||||
when(extensionVO.getName()).thenReturn("my-extnet");
|
||||
|
||||
Pair<Map<String, String>, Map<String, String>> details = new Pair<>(new HashMap<>(), new HashMap<>());
|
||||
when(extensionCustomActionDetailsDao.listDetailsKeyPairsWithVisibility(2L)).thenReturn(details);
|
||||
|
||||
// No provider found for any service
|
||||
when(networkServiceMapDao.getProviderForServiceInNetwork(eq(5L), any())).thenReturn(null);
|
||||
|
||||
CustomActionResultResponse resp = extensionsManager.runNetworkCustomAction(
|
||||
network, actionVO, extensionVO,
|
||||
ExtensionCustomAction.ResourceType.Network, new HashMap<>());
|
||||
|
||||
assertFalse(resp.isSuccess());
|
||||
assertTrue(resp.getResult().get(ApiConstants.DETAILS).contains("No network service provider"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runNetworkCustomActionFailsWhenProviderReturnsNull() {
|
||||
Network network = mock(Network.class);
|
||||
when(network.getId()).thenReturn(5L);
|
||||
when(network.getName()).thenReturn("test-net");
|
||||
|
||||
ExtensionCustomActionVO actionVO = mock(ExtensionCustomActionVO.class);
|
||||
when(actionVO.getUuid()).thenReturn("action-uuid");
|
||||
when(actionVO.getName()).thenReturn("unknown-action");
|
||||
when(actionVO.getId()).thenReturn(3L);
|
||||
when(actionVO.getSuccessMessage()).thenReturn(null);
|
||||
when(actionVO.getErrorMessage()).thenReturn(null);
|
||||
|
||||
ExtensionVO extensionVO = mock(ExtensionVO.class);
|
||||
when(extensionVO.getName()).thenReturn("my-extnet");
|
||||
|
||||
Pair<Map<String, String>, Map<String, String>> details = new Pair<>(new HashMap<>(), new HashMap<>());
|
||||
when(extensionCustomActionDetailsDao.listDetailsKeyPairsWithVisibility(3L)).thenReturn(details);
|
||||
|
||||
// networkServiceMapDao returns provider name
|
||||
when(networkServiceMapDao.getProviderForServiceInNetwork(eq(5L), any())).thenReturn("my-extnet");
|
||||
|
||||
// element implements both NetworkElement and NetworkCustomActionProvider but action returns null
|
||||
MockNetworkElement element = mock(MockNetworkElement.class);
|
||||
when(element.runCustomAction(eq(network), eq("unknown-action"), any())).thenReturn(null);
|
||||
when(networkModel.getElementImplementingProvider("my-extnet")).thenReturn(element);
|
||||
|
||||
CustomActionResultResponse resp = extensionsManager.runNetworkCustomAction(
|
||||
network, actionVO, extensionVO,
|
||||
ExtensionCustomAction.ResourceType.Network, new HashMap<>());
|
||||
|
||||
assertFalse(resp.isSuccess());
|
||||
assertTrue(resp.getResult().get(ApiConstants.DETAILS).contains("Action failed"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ public class ManagementServerMock {
|
|||
}
|
||||
}
|
||||
if (_znet.getState() != PhysicalNetwork.State.Enabled) {
|
||||
_znet = _networkService.updatePhysicalNetwork(_znet.getId(), null, null, null, PhysicalNetwork.State.Enabled.toString());
|
||||
_znet = _networkService.updatePhysicalNetwork(_znet.getId(), null, null, null, PhysicalNetwork.State.Enabled.toString(), null);
|
||||
}
|
||||
|
||||
// Ensure that the physical network supports Guest traffic.
|
||||
|
|
|
|||
|
|
@ -1559,6 +1559,10 @@ public class ApiDBUtils {
|
|||
return s_networkModel.canElementEnableIndividualServices(serviceProvider);
|
||||
}
|
||||
|
||||
public static boolean canElementEnableIndividualServicesByName(String providerName) {
|
||||
return s_networkModel.canElementEnableIndividualServicesByName(providerName);
|
||||
}
|
||||
|
||||
public static Pair<Long, Boolean> getDomainNetworkDetails(long networkId) {
|
||||
NetworkDomainVO map = s_networkDomainDao.getDomainNetworkMapByNetworkId(networkId);
|
||||
|
||||
|
|
|
|||
|
|
@ -548,6 +548,8 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
|
|||
ResourceIconManager resourceIconManager;
|
||||
@Inject
|
||||
AsyncJobDao asyncJobDao;
|
||||
@Inject
|
||||
NetworkModel networkModel;
|
||||
|
||||
public static String getPrettyDomainPath(String path) {
|
||||
if (path == null) {
|
||||
|
|
@ -3307,7 +3309,7 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
|
|||
}
|
||||
response.setServices(services);
|
||||
|
||||
Provider serviceProvider = Provider.getProvider(result.getProviderName());
|
||||
Provider serviceProvider = networkModel.resolveProvider(result.getProviderName());
|
||||
boolean canEnableIndividualServices = ApiDBUtils.canElementEnableIndividualServices(serviceProvider);
|
||||
response.setCanEnableIndividualServices(canEnableIndividualServices);
|
||||
|
||||
|
|
|
|||
|
|
@ -7268,7 +7268,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
}
|
||||
for (final String prvNameStr : svcPrv.get(serviceStr)) {
|
||||
// check if provider is supported
|
||||
final Network.Provider provider = Network.Provider.getProvider(prvNameStr);
|
||||
final Network.Provider provider = _networkModel.resolveProvider(prvNameStr);
|
||||
if (provider == null) {
|
||||
throw new InvalidParameterValueException("Invalid service provider: " + prvNameStr);
|
||||
}
|
||||
|
|
@ -7954,7 +7954,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
|||
// 1) Vaidate the detail values - have to match the lb provider
|
||||
// name
|
||||
final String providerStr = details.get(detail);
|
||||
if (Network.Provider.getProvider(providerStr) == null) {
|
||||
if (_networkModel.resolveProvider(providerStr) == null) {
|
||||
throw new InvalidParameterValueException("Invalid value " + providerStr + " for the detail " + detail);
|
||||
}
|
||||
if (serviceProviderMap.get(Service.Lb) != null) {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ import com.cloud.network.element.IpDeployingRequester;
|
|||
import com.cloud.network.element.NetworkElement;
|
||||
import com.cloud.network.element.UserDataServiceProvider;
|
||||
import com.cloud.network.router.VirtualRouter;
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
import org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement;
|
||||
import com.cloud.network.rules.FirewallRule.Purpose;
|
||||
import com.cloud.network.rules.FirewallRuleVO;
|
||||
import com.cloud.network.rules.dao.PortForwardingRulesDao;
|
||||
|
|
@ -234,6 +236,11 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
private NetworkService _networkService;
|
||||
@Inject
|
||||
TungstenGuestNetworkIpAddressDao tungstenGuestNetworkIpAddressDao;
|
||||
@Inject
|
||||
ExtensionHelper extensionHelper;
|
||||
@Inject
|
||||
private NetworkExtensionElement networkExtensionElement;
|
||||
|
||||
|
||||
private final HashMap<String, NetworkOfferingVO> _systemNetworks = new HashMap<String, NetworkOfferingVO>(5);
|
||||
|
||||
|
|
@ -262,15 +269,39 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
public NetworkElement getElementImplementingProvider(String providerName) {
|
||||
String elementName = s_providerToNetworkElementMap.get(providerName);
|
||||
NetworkElement element = AdapterBase.getAdapterByName(networkElements, elementName);
|
||||
if (element == null && extensionHelper.isNetworkExtensionProvider(providerName)) {
|
||||
// Provider is an extension-backed external network provider.
|
||||
// Initialize a copy of NetworkExtensionElement
|
||||
if (networkExtensionElement != null) {
|
||||
element = networkExtensionElement.withProviderName(providerName);
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective network capabilities for an extension-backed external
|
||||
* network provider on the given physical network.
|
||||
*
|
||||
* @param physicalNetworkId physical network ID (may be null for offering-level queries)
|
||||
* @param providerName provider / extension name
|
||||
* @return per-provider capabilities, or empty map if not found
|
||||
*/
|
||||
protected Map<Service, Map<Capability, String>> getExternalProviderCapabilities(
|
||||
Long physicalNetworkId, String providerName) {
|
||||
return extensionHelper.getNetworkCapabilitiesForProvider(physicalNetworkId, providerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Service> getElementServices(Provider provider) {
|
||||
NetworkElement element = getElementImplementingProvider(provider.getName());
|
||||
if (element == null) {
|
||||
throw new InvalidParameterValueException("Unable to find the Network Element implementing the Service Provider '" + provider.getName() + "'");
|
||||
}
|
||||
if (extensionHelper.isNetworkExtensionProvider(provider.getName())) {
|
||||
Map<Service, Map<Capability, String>> caps = getExternalProviderCapabilities(null, provider.getName());
|
||||
return new ArrayList<Service>(caps.keySet());
|
||||
}
|
||||
return new ArrayList<Service>(element.getCapabilities().keySet());
|
||||
}
|
||||
|
||||
|
|
@ -283,6 +314,24 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
return element.canEnableIndividualServices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canElementEnableIndividualServicesByName(String providerName) {
|
||||
if (providerName == null) {
|
||||
return false;
|
||||
}
|
||||
// Try resolve to enum Provider first
|
||||
Provider provider = resolveProvider(providerName);
|
||||
if (provider != null) {
|
||||
try {
|
||||
return canElementEnableIndividualServices(provider);
|
||||
} catch (Exception e) {
|
||||
logger.debug("canElementEnableIndividualServices failed for provider {}: {}", providerName, e.getMessage());
|
||||
}
|
||||
}
|
||||
// Unknown provider: be conservative and return false
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<Purpose> getPublicIpPurposeInRules(PublicIpAddress ip, boolean includeRevoked, boolean includingFirewall) {
|
||||
Set<Purpose> result = new HashSet<Purpose>();
|
||||
List<FirewallRuleVO> rules = null;
|
||||
|
|
@ -435,8 +484,11 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
if (providers == null) {
|
||||
providers = new HashSet<Provider>();
|
||||
}
|
||||
providers.add(Provider.getProvider(nsm.getProvider()));
|
||||
map.put(Service.getService(nsm.getService()), providers);
|
||||
Provider provider = resolveProvider(nsm.getProvider());
|
||||
if (provider != null) {
|
||||
providers.add(provider);
|
||||
map.put(Service.getService(nsm.getService()), providers);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
|
@ -481,16 +533,45 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
Map<Provider, Set<Service>> map = new HashMap<Provider, Set<Service>>();
|
||||
List<NetworkServiceMapVO> nsms = _ntwkSrvcDao.getServicesInNetwork(networkId);
|
||||
for (NetworkServiceMapVO nsm : nsms) {
|
||||
Set<Service> services = map.get(Provider.getProvider(nsm.getProvider()));
|
||||
Provider provider = resolveProvider(nsm.getProvider());
|
||||
if (provider == null) {
|
||||
continue;
|
||||
}
|
||||
Set<Service> services = map.get(provider);
|
||||
if (services == null) {
|
||||
services = new HashSet<Service>();
|
||||
}
|
||||
services.add(Service.getService(nsm.getService()));
|
||||
map.put(Provider.getProvider(nsm.getProvider()), services);
|
||||
map.put(provider, services);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a provider name to a {@link Provider} instance.
|
||||
*
|
||||
* <p>For well-known providers, returns the static constant from
|
||||
* {@link Provider#getProvider(String)}. For dynamic NetworkOrchestrator
|
||||
* extension providers (whose names are not in the static registry), returns
|
||||
* a transient {@link Provider} with the given name so that the caller can
|
||||
* still dispatch to the correct {@link NetworkExtensionElement} via
|
||||
* {@link #getElementImplementingProvider(String)}.</p>
|
||||
*
|
||||
* @param providerName the provider name from {@code ntwk_service_map}
|
||||
* @return a {@link Provider} instance, or {@code null} if not resolvable
|
||||
*/
|
||||
@Override
|
||||
public Provider resolveProvider(String providerName) {
|
||||
Provider provider = Provider.getProvider(providerName);
|
||||
if (provider == null && extensionHelper.isNetworkExtensionProvider(providerName)) {
|
||||
// Dynamic extension-backed provider: create a transient Provider that preserves
|
||||
// the actual extension name. getElementImplementingProvider() handles this name
|
||||
// by detecting it as an extension provider and returning NetworkExtensionElement.
|
||||
provider = Provider.createTransientProvider(providerName);
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Provider, ArrayList<PublicIpAddress>> getProviderToIpList(Network network, Map<PublicIpAddress, Set<Service>> ipToServices) {
|
||||
NetworkOffering offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
|
||||
|
|
@ -694,11 +775,21 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
|
||||
// list all services of this networkOffering
|
||||
List<NetworkServiceMapVO> servicesMap = _ntwkSrvcDao.getServicesInNetwork(networkId);
|
||||
|
||||
// Resolve the physical network once for external provider lookups
|
||||
NetworkVO network = _networksDao.findById(networkId);
|
||||
Long physicalNetworkId = network != null ? network.getPhysicalNetworkId() : null;
|
||||
|
||||
for (NetworkServiceMapVO instance : servicesMap) {
|
||||
Service service = Service.getService(instance.getService());
|
||||
NetworkElement element = getElementImplementingProvider(instance.getProvider());
|
||||
if (element != null) {
|
||||
Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
|
||||
Map<Service, Map<Capability, String>> elementCapabilities;
|
||||
if (extensionHelper.isNetworkExtensionProvider(instance.getProvider()) && physicalNetworkId != null) {
|
||||
elementCapabilities = getExternalProviderCapabilities(physicalNetworkId, instance.getProvider());
|
||||
} else {
|
||||
elementCapabilities = element.getCapabilities();
|
||||
}
|
||||
if (elementCapabilities != null) {
|
||||
networkCapabilities.put(service, elementCapabilities.get(service));
|
||||
}
|
||||
|
|
@ -724,10 +815,15 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
|
||||
NetworkElement element = getElementImplementingProvider(provider);
|
||||
if (element != null) {
|
||||
Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
|
||||
;
|
||||
Map<Service, Map<Capability, String>> elementCapabilities;
|
||||
if (extensionHelper.isNetworkExtensionProvider(provider)) {
|
||||
elementCapabilities = getExternalProviderCapabilities(null, provider);
|
||||
} else {
|
||||
elementCapabilities = element.getCapabilities();
|
||||
}
|
||||
|
||||
if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
|
||||
// TBD: We should be sending providerId and not the offering object itself.
|
||||
throw new UnsupportedServiceException("Service " + service.getName() + " is not supported by the element=" + element.getName() +
|
||||
" implementing Provider=" + provider);
|
||||
}
|
||||
|
|
@ -758,12 +854,22 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
// we have to calculate capabilities for all of them
|
||||
String provider = providers.get(0);
|
||||
|
||||
// Check if this is an extension-backed external network provider first.
|
||||
// These providers are not in the static s_providerToNetworkElementMap so
|
||||
// we resolve their capabilities from the extension details directly.
|
||||
if (extensionHelper.isNetworkExtensionProvider(provider)) {
|
||||
Map<Service, Map<Capability, String>> extCaps = getExternalProviderCapabilities(null, provider);
|
||||
if (extCaps != null && extCaps.containsKey(service)) {
|
||||
return extCaps.get(service);
|
||||
}
|
||||
return serviceCapabilities;
|
||||
}
|
||||
|
||||
// FIXME we return the capabilities of the first provider of the service - what if we have multiple providers
|
||||
// for same Service?
|
||||
NetworkElement element = getElementImplementingProvider(provider);
|
||||
if (element != null) {
|
||||
Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
|
||||
;
|
||||
|
||||
if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
|
||||
// TBD: We should be sending providerId and not the offering object itself.
|
||||
|
|
@ -979,7 +1085,6 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean areServicesSupportedByNetworkOffering(long networkOfferingId, Service... services) {
|
||||
return (_ntwkOfferingSrvcDao.areServicesSupportedByNetworkOffering(networkOfferingId, services));
|
||||
|
|
@ -1161,7 +1266,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
if (providers == null) {
|
||||
providers = new HashSet<Provider>();
|
||||
}
|
||||
providers.add(Provider.getProvider(instance.getProvider()));
|
||||
providers.add(resolveProvider(instance.getProvider()));
|
||||
serviceProviderMap.put(Service.getService(service), providers);
|
||||
}
|
||||
|
||||
|
|
@ -1201,9 +1306,76 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
}
|
||||
}
|
||||
|
||||
// Also include extension-backed NetworkExtension providers registered in
|
||||
// physical_network_service_providers whose provider name matches a registered
|
||||
// NetworkOrchestrator extension (detected via extensionHelper.isNetworkExtensionProvider).
|
||||
//
|
||||
// We use _pNSPDao.listBy(physNetId) to enumerate all NSP entries, then check
|
||||
// each provider name against the extension registry. This avoids a separate
|
||||
// pass over all physical-network/extension combinations.
|
||||
// resolveProvider() creates a transient Provider (not added to the static list)
|
||||
// for extension names that are not in the built-in registry.
|
||||
try {
|
||||
List<PhysicalNetworkVO> physNets = _physicalNetworkDao.listAll();
|
||||
if (physNets != null) {
|
||||
// Use a set to avoid adding the same provider name twice (multiple phys-nets)
|
||||
Set<String> addedExtProviders = new HashSet<>();
|
||||
for (PhysicalNetworkVO physNet : physNets) {
|
||||
List<com.cloud.network.dao.PhysicalNetworkServiceProviderVO> nsps =
|
||||
_pNSPDao.listBy(physNet.getId());
|
||||
if (nsps == null) continue;
|
||||
for (PhysicalNetworkServiceProviderVO nsp : nsps) {
|
||||
String provName = nsp.getProviderName();
|
||||
if (provName == null || addedExtProviders.contains(provName)) continue;
|
||||
if (!extensionHelper.isNetworkExtensionProvider(provName)) continue;
|
||||
|
||||
// Filter by service if requested: check the NSP's service flags
|
||||
if (service != null && !isServiceProvidedByNsp(nsp, service)) continue;
|
||||
|
||||
// Resolve or create a transient Provider for the extension name.
|
||||
Provider extProvider = resolveProvider(provName);
|
||||
if (extProvider == null) continue;
|
||||
supportedProviders.add(extProvider);
|
||||
addedExtProviders.add(provName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed to include extension-backed providers in listSupportedNetworkServiceProviders: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return new ArrayList<Provider>(supportedProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given {@link com.cloud.network.dao.PhysicalNetworkServiceProviderVO}
|
||||
* has its service flag set for {@code service}.
|
||||
*
|
||||
* <p>This is used by {@link #listSupportedNetworkServiceProviders} to filter extension-backed
|
||||
* providers (looked up via {@link com.cloud.network.dao.PhysicalNetworkServiceProviderDao#listBy})
|
||||
* without having to query each extension's capability JSON.</p>
|
||||
*/
|
||||
private boolean isServiceProvidedByNsp(
|
||||
PhysicalNetworkServiceProviderVO nsp, Service service) {
|
||||
if (service == null) {
|
||||
return true;
|
||||
}
|
||||
if (service == Service.Dhcp) return nsp.isDhcpServiceProvided();
|
||||
if (service == Service.Dns) return nsp.isDnsServiceProvided();
|
||||
if (service == Service.Firewall) return nsp.isFirewallServiceProvided();
|
||||
if (service == Service.Gateway) return nsp.isGatewayServiceProvided();
|
||||
if (service == Service.Lb) return nsp.isLbServiceProvided();
|
||||
if (service == Service.PortForwarding) return nsp.isPortForwardingServiceProvided();
|
||||
if (service == Service.SecurityGroup) return nsp.isSecuritygroupServiceProvided();
|
||||
if (service == Service.SourceNat) return nsp.isSourcenatServiceProvided();
|
||||
if (service == Service.StaticNat) return nsp.isStaticnatServiceProvided();
|
||||
if (service == Service.UserData) return nsp.isUserdataServiceProvided();
|
||||
if (service == Service.Vpn) return nsp.isVpnServiceProvided();
|
||||
if (service == Service.NetworkACL) return nsp.isNetworkAclServiceProvided();
|
||||
// Unknown service: fall back to true so extension-backed providers are not filtered out
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Provider getDefaultUniqueProviderForService(String serviceName) {
|
||||
List<? extends Provider> providers = listSupportedNetworkServiceProviders(serviceName);
|
||||
|
|
@ -1540,13 +1712,19 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
throw new InvalidParameterValueException("Unable to find the Network Element implementing the Service Provider '" + provider.getName() + "'");
|
||||
}
|
||||
|
||||
// For external network providers, get per-provider capabilities
|
||||
final boolean isExternal = extensionHelper.isNetworkExtensionProvider(provider.getName());
|
||||
Map<Service, Map<Capability, String>> providerCaps = isExternal
|
||||
? getExternalProviderCapabilities(null, provider.getName())
|
||||
: element.getCapabilities();
|
||||
|
||||
Set<Service> enabledServices = new HashSet<Service>();
|
||||
enabledServices.addAll(providersMap.get(provider));
|
||||
|
||||
if (enabledServices != null && !enabledServices.isEmpty()) {
|
||||
if (!element.canEnableIndividualServices()) {
|
||||
if (!element.canEnableIndividualServices() && !isExternal) {
|
||||
Set<Service> requiredServices = new HashSet<Service>();
|
||||
requiredServices.addAll(element.getCapabilities().keySet());
|
||||
requiredServices.addAll(providerCaps.keySet());
|
||||
|
||||
if (requiredServices.contains(Network.Service.Gateway)) {
|
||||
requiredServices.remove(Network.Service.Gateway);
|
||||
|
|
@ -1580,7 +1758,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
List<String> serviceList = new ArrayList<String>();
|
||||
for (Service service : enabledServices) {
|
||||
// check if the service is provided by this Provider
|
||||
if (!element.getCapabilities().containsKey(service)) {
|
||||
if (!providerCaps.containsKey(service)) {
|
||||
throw new UnsupportedServiceException(provider.getName() + " Provider cannot provide service " + service.getName());
|
||||
}
|
||||
serviceList.add(service.getName());
|
||||
|
|
@ -1655,18 +1833,32 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean providerSupportsCapability(Set<Provider> providers, Service service, Capability cap) {
|
||||
for (Provider provider : providers) {
|
||||
NetworkElement element = getElementImplementingProvider(provider.getName());
|
||||
if (element != null) {
|
||||
Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
|
||||
boolean isExtProvider = extensionHelper.isNetworkExtensionProvider(provider.getName());
|
||||
Map<Service, Map<Capability, String>> elementCapabilities = isExtProvider
|
||||
? getExternalProviderCapabilities(null, provider.getName())
|
||||
: element.getCapabilities();
|
||||
if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
|
||||
if (isExtProvider) {
|
||||
// Extension provider with no declared capabilities for this service —
|
||||
// treat as "service supported but capability not constrained"
|
||||
return false;
|
||||
}
|
||||
throw new UnsupportedServiceException("Service " + service.getName() + " is not supported by the element=" + element.getName() +
|
||||
" implementing Provider=" + provider.getName());
|
||||
}
|
||||
Map<Capability, String> serviceCapabilities = elementCapabilities.get(service);
|
||||
if (serviceCapabilities == null || serviceCapabilities.isEmpty()) {
|
||||
if (isExtProvider) {
|
||||
// Extension provider declared the service but without specific capability
|
||||
// constraints — treat as "capability not constrained, not explicitly supported"
|
||||
return false;
|
||||
}
|
||||
throw new UnsupportedServiceException("Service " + service.getName() + " doesn't have capabilites for element=" + element.getName() +
|
||||
" implementing Provider=" + provider.getName());
|
||||
}
|
||||
|
|
@ -1686,19 +1878,36 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
for (Provider provider : providers) {
|
||||
NetworkElement element = getElementImplementingProvider(provider.getName());
|
||||
if (element != null) {
|
||||
Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
|
||||
boolean isExtProvider = extensionHelper.isNetworkExtensionProvider(provider.getName());
|
||||
Map<Service, Map<Capability, String>> elementCapabilities = isExtProvider
|
||||
? getExternalProviderCapabilities(null, provider.getName())
|
||||
: element.getCapabilities();
|
||||
if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
|
||||
if (isExtProvider) {
|
||||
// Extension provider with no declared capabilities for this service —
|
||||
// treat as supported without constraints; skip the capability check.
|
||||
continue;
|
||||
}
|
||||
throw new UnsupportedServiceException("Service " + service.getName() + " is not supported by the element=" + element.getName() +
|
||||
" implementing Provider=" + provider.getName());
|
||||
}
|
||||
Map<Capability, String> serviceCapabilities = elementCapabilities.get(service);
|
||||
if (serviceCapabilities == null || serviceCapabilities.isEmpty()) {
|
||||
if (isExtProvider) {
|
||||
// Extension provider declared the service without capability constraints —
|
||||
// accept any capability value (the extension handles it at runtime).
|
||||
continue;
|
||||
}
|
||||
throw new UnsupportedServiceException("Service " + service.getName() + " doesn't have capabilities for element=" + element.getName() +
|
||||
" implementing Provider=" + provider.getName());
|
||||
}
|
||||
|
||||
String value = serviceCapabilities.get(cap);
|
||||
if (value == null || value.isEmpty()) {
|
||||
if (isExtProvider) {
|
||||
// Capability not explicitly declared for this extension — accept it.
|
||||
continue;
|
||||
}
|
||||
throw new UnsupportedServiceException("Service " + service.getName() + " doesn't have capability " + cap.getName() + " for element=" +
|
||||
element.getName() + " implementing Provider=" + provider.getName());
|
||||
}
|
||||
|
|
@ -1953,7 +2162,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
List<String> providerNames = _ntwkOfferingSrvcDao.getDistinctProviders(ntkwOffId);
|
||||
List<Provider> providers = new ArrayList<Provider>();
|
||||
for (String providerName : providerNames) {
|
||||
providers.add(Network.Provider.getProvider(providerName));
|
||||
providers.add(resolveProvider(providerName));
|
||||
}
|
||||
|
||||
return providers;
|
||||
|
|
@ -2255,7 +2464,7 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
|
|||
Map<String, Provider> providers = new HashMap<String, Provider>();
|
||||
for (String providerName : providerNames) {
|
||||
if (!providers.containsKey(providerName)) {
|
||||
providers.put(providerName, Network.Provider.getProvider(providerName));
|
||||
providers.put(providerName, resolveProvider(providerName));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ import org.apache.cloudstack.api.command.user.vm.ListNicsCmd;
|
|||
import org.apache.cloudstack.api.response.AcquirePodIpCmdResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.extension.ExtensionResourceMap;
|
||||
import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
|
|
@ -400,6 +402,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
|
|||
@Inject
|
||||
NetworkDetailsDao _networkDetailsDao;
|
||||
@Inject
|
||||
ExtensionsManager extensionsManager;
|
||||
@Inject
|
||||
LoadBalancerDao _loadBalancerDao;
|
||||
@Inject
|
||||
NetworkMigrationManager _networkMigrationManager;
|
||||
|
|
@ -3087,8 +3091,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
|
|||
|
||||
protected boolean providersConfiguredForExternalNetworking(Collection<String> providers) {
|
||||
for (String providerStr : providers) {
|
||||
Provider provider = Network.Provider.getProvider(providerStr);
|
||||
if (provider.isExternal()) {
|
||||
Provider provider = _networkModel.resolveProvider(providerStr);
|
||||
if (provider != null && provider.isExternal()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -4395,7 +4399,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
|
|||
@Override
|
||||
@DB
|
||||
@ActionEvent(eventType = EventTypes.EVENT_PHYSICAL_NETWORK_UPDATE, eventDescription = "updating physical network", async = true)
|
||||
public PhysicalNetwork updatePhysicalNetwork(Long id, String networkSpeed, List<String> tags, String newVnetRange, String state) {
|
||||
public PhysicalNetwork updatePhysicalNetwork(Long id, String networkSpeed, List<String> tags, String newVnetRange, String state, Map<String, String> externalDetails) {
|
||||
|
||||
// verify input parameters
|
||||
PhysicalNetworkVO network = _physicalNetworkDao.findById(id);
|
||||
|
|
@ -4449,6 +4453,29 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
|
|||
addOrRemoveVnets(listOfRanges, network);
|
||||
}
|
||||
_physicalNetworkDao.update(id, network);
|
||||
|
||||
// If external details provided, and an extension is registered on this physical network,
|
||||
// update the extension_resource_map_details accordingly.
|
||||
try {
|
||||
if (externalDetails != null && !externalDetails.isEmpty()) {
|
||||
Pair<Boolean, ExtensionResourceMap> needDetailsUpdateMapPair =
|
||||
extensionsManager.extensionResourceMapDetailsNeedUpdate(id,
|
||||
ExtensionResourceMap.ResourceType.PhysicalNetwork, externalDetails);
|
||||
if (Boolean.TRUE.equals(needDetailsUpdateMapPair.first())) {
|
||||
ExtensionResourceMap extensionResourceMap = needDetailsUpdateMapPair.second();
|
||||
if (extensionResourceMap == null) {
|
||||
throw new InvalidParameterValueException(
|
||||
String.format("Physical network: %s is not registered with any extension, details cannot be updated",
|
||||
network.getId()));
|
||||
}
|
||||
extensionsManager.updateExtensionResourceMapDetails(extensionResourceMap.getId(), externalDetails);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Log warning but don't fail the update
|
||||
logger.warn("Failed to update external details for physical network {}: {}", id, e.getMessage());
|
||||
}
|
||||
|
||||
return network;
|
||||
|
||||
}
|
||||
|
|
@ -5121,7 +5148,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
|
|||
|
||||
Provider provider = null;
|
||||
if (providerName != null) {
|
||||
provider = Network.Provider.getProvider(providerName);
|
||||
provider = _networkModel.resolveProvider(providerName);
|
||||
if (provider == null) {
|
||||
throw new InvalidParameterValueException("Invalid Network Service Provider=" + providerName);
|
||||
}
|
||||
|
|
@ -5158,7 +5185,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
|
|||
}
|
||||
|
||||
if (providerName != null) {
|
||||
Provider provider = Network.Provider.getProvider(providerName);
|
||||
Provider provider = _networkModel.resolveProvider(providerName);
|
||||
if (provider == null) {
|
||||
throw new InvalidParameterValueException("Invalid Network Service Provider=" + providerName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1468,7 +1468,7 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
|||
}
|
||||
|
||||
// Validate Provider
|
||||
Network.Provider provider = Network.Provider.getProvider(cmd.getProvider());
|
||||
Network.Provider provider = networkModel.resolveProvider(cmd.getProvider());
|
||||
if (provider == null) {
|
||||
throw new InvalidParameterValueException("The Provider " + cmd.getProvider() + " does not exist; Unable to create Counter");
|
||||
}
|
||||
|
|
@ -1537,7 +1537,7 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
|||
}
|
||||
String providerStr = cmd.getProvider();
|
||||
if (providerStr != null) {
|
||||
Network.Provider provider = Network.Provider.getProvider(providerStr);
|
||||
Network.Provider provider = networkModel.resolveProvider(providerStr);
|
||||
if (provider == null) {
|
||||
throw new InvalidParameterValueException("The Provider " + providerStr + " does not exist; Unable to list Counter");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,9 +69,11 @@ import com.cloud.network.dao.FirewallRulesDcidrsDao;
|
|||
import com.cloud.network.dao.IPAddressDao;
|
||||
import com.cloud.network.dao.IPAddressVO;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
import com.cloud.network.dao.NetworkServiceMapDao;
|
||||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.network.element.FirewallServiceProvider;
|
||||
import com.cloud.network.element.NetworkACLServiceProvider;
|
||||
import com.cloud.network.element.NetworkElement;
|
||||
import com.cloud.network.element.PortForwardingServiceProvider;
|
||||
import com.cloud.network.element.StaticNatServiceProvider;
|
||||
import com.cloud.network.rules.FirewallManager;
|
||||
|
|
@ -150,6 +152,9 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
|
|||
EntityManager entityManager;
|
||||
@Inject
|
||||
NsxProviderDao nsxProviderDao;
|
||||
@Inject
|
||||
NetworkServiceMapDao networkServiceMapDao;
|
||||
|
||||
List<FirewallServiceProvider> _firewallElements;
|
||||
|
||||
List<PortForwardingServiceProvider> _pfElements;
|
||||
|
|
@ -617,12 +622,25 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
|
|||
String supportedProtocols;
|
||||
String supportedTrafficTypes = null;
|
||||
if (purpose == FirewallRule.Purpose.Firewall) {
|
||||
supportedTrafficTypes = caps.get(Capability.SupportedTrafficDirection).toLowerCase();
|
||||
String supportedTrafficTypesStr = caps.get(Capability.SupportedTrafficDirection);
|
||||
if (supportedTrafficTypesStr == null) {
|
||||
throw new CloudRuntimeException("Supported traffic direction capability is not defined for Firewall service");
|
||||
}
|
||||
supportedTrafficTypes = supportedTrafficTypesStr.toLowerCase();
|
||||
}
|
||||
|
||||
if (purpose == FirewallRule.Purpose.Firewall && trafficType == FirewallRule.TrafficType.Egress) {
|
||||
// throw an exception if cap is not found
|
||||
String supportedProtocolsStr = caps.get(Capability.SupportedEgressProtocols);
|
||||
if (supportedProtocolsStr == null) {
|
||||
throw new CloudRuntimeException("Supported egress protocols capability is not defined for Firewall service");
|
||||
}
|
||||
supportedProtocols = caps.get(Capability.SupportedEgressProtocols).toLowerCase();
|
||||
} else {
|
||||
String supportedProtocolsStr = caps.get(Capability.SupportedProtocols);
|
||||
if (supportedProtocolsStr == null) {
|
||||
throw new CloudRuntimeException("Supported protocols capability is not defined for " + purpose + " service");
|
||||
}
|
||||
supportedProtocols = caps.get(Capability.SupportedProtocols).toLowerCase();
|
||||
}
|
||||
|
||||
|
|
@ -700,18 +718,34 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
|
|||
if (handled)
|
||||
break;
|
||||
}
|
||||
if (!handled) {
|
||||
// Get provider name and get the element by provider name (it could be an external provider)
|
||||
String fwProviderName = networkServiceMapDao.getProviderForServiceInNetwork(network.getId(), Service.Firewall);
|
||||
if (fwProviderName != null) {
|
||||
NetworkElement element = _networkModel.getElementImplementingProvider(fwProviderName);
|
||||
handled = ((FirewallServiceProvider) element).applyFWRules(network, rules);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PortForwarding:
|
||||
for (PortForwardingServiceProvider element : _pfElements) {
|
||||
for (PortForwardingServiceProvider element : _pfElements) {
|
||||
Network.Provider provider = element.getProvider();
|
||||
boolean isPfProvider = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.PortForwarding, provider);
|
||||
if (!isPfProvider) {
|
||||
continue;
|
||||
}
|
||||
handled = element.applyPFRules(network, (List<PortForwardingRule>)rules);
|
||||
handled = element.applyPFRules(network, (List<PortForwardingRule>)rules);
|
||||
if (handled)
|
||||
break;
|
||||
}
|
||||
if (!handled) {
|
||||
// Get provider name and get the element by provider name (it could be an external provider)
|
||||
String pfProviderName = networkServiceMapDao.getProviderForServiceInNetwork(network.getId(), Service.PortForwarding);
|
||||
if (pfProviderName != null) {
|
||||
NetworkElement element = _networkModel.getElementImplementingProvider(pfProviderName);
|
||||
handled = ((PortForwardingServiceProvider) element).applyPFRules(network, (List<PortForwardingRule>) rules);
|
||||
}
|
||||
}
|
||||
break;
|
||||
/* case NetworkACL:
|
||||
for (NetworkACLServiceProvider element: _networkAclElements) {
|
||||
|
|
@ -726,7 +760,7 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
|
|||
}
|
||||
break;*/
|
||||
default:
|
||||
assert (false) : "Unexpected fall through in applying rules to the network elements";
|
||||
assert (false) : "Unexpected fall through in applying rules to the network elements";
|
||||
logger.error("FirewallManager cannot process rules of type " + purpose);
|
||||
throw new CloudRuntimeException("FirewallManager cannot process rules of type " + purpose);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ import com.cloud.network.dao.NetworkServiceMapDao;
|
|||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.network.dao.SslCertVO;
|
||||
import com.cloud.network.element.LoadBalancingServiceProvider;
|
||||
import com.cloud.network.element.NetworkElement;
|
||||
import com.cloud.network.lb.LoadBalancingRule.LbAutoScalePolicy;
|
||||
import com.cloud.network.lb.LoadBalancingRule.LbAutoScaleVmGroup;
|
||||
import com.cloud.network.lb.LoadBalancingRule.LbAutoScaleVmProfile;
|
||||
|
|
@ -2049,6 +2050,14 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
|
|||
if (handled)
|
||||
break;
|
||||
}
|
||||
if (!handled) {
|
||||
// Get provider name and get the element by provider name (it could be an external provider)
|
||||
String lbProviderName = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.Lb);
|
||||
if (lbProviderName != null) {
|
||||
NetworkElement element = _networkModel.getElementImplementingProvider(lbProviderName);
|
||||
handled = ((LoadBalancingServiceProvider) element).applyLBRules(network, rules);
|
||||
}
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ import com.cloud.network.Network;
|
|||
import com.cloud.network.Network.Service;
|
||||
import com.cloud.network.NetworkModel;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
import com.cloud.network.dao.NetworkServiceMapDao;
|
||||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.network.element.NetworkACLServiceProvider;
|
||||
import com.cloud.network.element.NetworkElement;
|
||||
import com.cloud.network.element.VpcProvider;
|
||||
import com.cloud.network.vpc.NetworkACLItem.State;
|
||||
import com.cloud.network.vpc.dao.NetworkACLDao;
|
||||
|
|
@ -75,6 +77,8 @@ public class NetworkACLManagerImpl extends ManagerBase implements NetworkACLMana
|
|||
private MessageBus _messageBus;
|
||||
@Inject
|
||||
private ResourceTagDao resourceTagDao;
|
||||
@Inject
|
||||
NetworkServiceMapDao networkServiceMapDao;
|
||||
|
||||
private List<NetworkACLServiceProvider> _networkAclElements;
|
||||
|
||||
|
|
@ -442,12 +446,23 @@ public class NetworkACLManagerImpl extends ManagerBase implements NetworkACLMana
|
|||
logger.debug("Applying NetworkACL for network: {} with Network ACL service provider", network);
|
||||
handled = element.applyNetworkACLs(network, rules);
|
||||
if (handled) {
|
||||
// publish message on message bus, so that network elements implementing distributed routing
|
||||
// capability can act on the event
|
||||
_messageBus.publish(_name, "Network_ACL_Replaced", PublishScope.LOCAL, network);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundProvider) {
|
||||
// Get provider name and get the element by provider name (it could be an external provider)
|
||||
String aclProviderName = networkServiceMapDao.getProviderForServiceInNetwork(network.getId(), Service.NetworkACL);
|
||||
if (aclProviderName != null) {
|
||||
foundProvider = true;
|
||||
NetworkElement element = _networkModel.getElementImplementingProvider(aclProviderName);
|
||||
handled = ((NetworkACLServiceProvider) element).applyNetworkACLs(network, rules);
|
||||
}
|
||||
}
|
||||
if (handled) {
|
||||
// publish message on message bus, so that network elements implementing distributed routing
|
||||
// capability can act on the event
|
||||
_messageBus.publish(_name, "Network_ACL_Replaced", PublishScope.LOCAL, network);
|
||||
}
|
||||
if (!foundProvider) {
|
||||
logger.debug("Unable to find NetworkACL service provider for network: {}", network);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ import org.apache.cloudstack.api.command.user.vpc.RestartVPCCmd;
|
|||
import org.apache.cloudstack.api.command.user.vpc.UpdateVPCCmd;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.extension.Extension;
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
|
|
@ -329,6 +331,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
Site2SiteVpnConnectionDao site2SiteVpnConnectionDao;
|
||||
@Inject
|
||||
Site2SiteCustomerGatewayDao site2SiteCustomerGatewayDao;
|
||||
@Inject
|
||||
ExtensionHelper extensionHelper;
|
||||
|
||||
private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("VpcChecker"));
|
||||
private List<VpcProvider> vpcElements = null;
|
||||
|
|
@ -701,7 +705,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
final Set<Provider> providers = new HashSet<Provider>();
|
||||
for (final String prvNameStr : serviceEntry.getValue()) {
|
||||
// check if provider is supported
|
||||
final Network.Provider provider = Network.Provider.getProvider(prvNameStr);
|
||||
final Network.Provider provider = _ntwkModel.resolveProvider(prvNameStr);
|
||||
if (provider == null) {
|
||||
throw new InvalidParameterValueException("Invalid service provider: " + prvNameStr);
|
||||
}
|
||||
|
|
@ -1248,12 +1252,18 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
|
||||
for (final VpcOfferingServiceMapVO instance : map) {
|
||||
final Service service = Service.getService(instance.getService());
|
||||
if (service == null) {
|
||||
continue;
|
||||
}
|
||||
Set<Provider> providers;
|
||||
providers = serviceProviderMap.get(service);
|
||||
if (providers == null) {
|
||||
providers = new HashSet<Provider>();
|
||||
}
|
||||
providers.add(Provider.getProvider(instance.getProvider()));
|
||||
final Provider provider = _ntwkModel.resolveProvider(instance.getProvider());
|
||||
if (provider != null) {
|
||||
providers.add(provider);
|
||||
}
|
||||
serviceProviderMap.put(service, providers);
|
||||
}
|
||||
|
||||
|
|
@ -1846,6 +1856,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
if (provider == null) {
|
||||
// Default to VPCVirtualRouter
|
||||
provider = Provider.VPCVirtualRouter.getName();
|
||||
} else {
|
||||
provider = _ntwkModel.resolveProvider(provider).getName();
|
||||
}
|
||||
|
||||
if (!_ntwkModel.isProviderEnabledInZone(zoneId, provider)) {
|
||||
|
|
@ -2035,9 +2047,12 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
if (! userIps.isEmpty()) {
|
||||
try {
|
||||
_ipAddrMgr.updateSourceNatIpAddress(requestedIp, userIps);
|
||||
if (isVpcForProvider(Provider.Nsx, vpc) || isVpcForProvider(Provider.Netris, vpc)) {
|
||||
if (isVpcForProvider(Provider.Nsx, vpc) || isVpcForProvider(Provider.Netris, vpc)
|
||||
|| isVpcForProvider(Provider.NetworkExtension, vpc)) {
|
||||
boolean isForNsx = _vpcOffSvcMapDao.isProviderForVpcOffering(Provider.Nsx, vpc.getVpcOfferingId());
|
||||
String providerName = isForNsx ? Provider.Nsx.getName() : Provider.Netris.getName();
|
||||
boolean isForNetris = _vpcOffSvcMapDao.isProviderForVpcOffering(Provider.Netris, vpc.getVpcOfferingId());
|
||||
String providerName = isForNsx ? Provider.Nsx.getName()
|
||||
: (isForNetris ? Provider.Netris.getName() : Provider.NetworkExtension.getName());
|
||||
VpcProvider providerElement = (VpcProvider) _ntwkModel.getElementImplementingProvider(providerName);
|
||||
if (Objects.nonNull(providerElement)) {
|
||||
providerElement.updateVpcSourceNatIp(vpc, requestedIp);
|
||||
|
|
@ -2511,7 +2526,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
// 1) in current release, only vpc provider is supported by Vpc offering
|
||||
final List<Provider> providers = _ntwkModel.getNtwkOffDistinctProviders(guestNtwkOff.getId());
|
||||
for (final Provider provider : providers) {
|
||||
if (!supportedProviders.contains(provider)) {
|
||||
if (!supportedProviders.contains(provider)
|
||||
&& !extensionHelper.isNetworkExtensionProvider(provider.getName())) {
|
||||
throw new InvalidParameterValueException("Provider of type " + provider.getName() + " is not supported for network offerings that can be used in VPC");
|
||||
}
|
||||
}
|
||||
|
|
@ -2618,17 +2634,32 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
}
|
||||
|
||||
public List<VpcProvider> getVpcElements() {
|
||||
// Static providers (VPCVirtualRouter, JuniperContrailVpcRouter) are initialized once.
|
||||
if (vpcElements == null) {
|
||||
vpcElements = new ArrayList<VpcProvider>();
|
||||
vpcElements.add((VpcProvider) _ntwkModel.getElementImplementingProvider(Provider.VPCVirtualRouter.getName()));
|
||||
vpcElements.add((VpcProvider) _ntwkModel.getElementImplementingProvider(Provider.JuniperContrailVpcRouter.getName()));
|
||||
final NetworkElement vpcVirtualRouter = _ntwkModel.getElementImplementingProvider(Provider.VPCVirtualRouter.getName());
|
||||
if (vpcVirtualRouter instanceof VpcProvider) {
|
||||
vpcElements.add((VpcProvider) vpcVirtualRouter);
|
||||
}
|
||||
|
||||
final NetworkElement contrailVpcRouter = _ntwkModel.getElementImplementingProvider(Provider.JuniperContrailVpcRouter.getName());
|
||||
if (contrailVpcRouter instanceof VpcProvider) {
|
||||
vpcElements.add((VpcProvider) contrailVpcRouter);
|
||||
}
|
||||
}
|
||||
|
||||
if (vpcElements == null) {
|
||||
throw new CloudRuntimeException("Failed to initialize vpc elements");
|
||||
// Extension-backed providers are re-fetched every call so that dynamically
|
||||
// registered extensions are picked up without requiring a server restart.
|
||||
final List<VpcProvider> result = new ArrayList<>(vpcElements);
|
||||
for (final Extension extension : extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator)) {
|
||||
final String providerName = extension.getName();
|
||||
final NetworkElement element = _ntwkModel.getElementImplementingProvider(providerName);
|
||||
if (element instanceof VpcProvider) {
|
||||
result.add((VpcProvider) element);
|
||||
}
|
||||
}
|
||||
|
||||
return vpcElements;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3937,7 +3968,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
final Map<String, Provider> providers = new HashMap<String, Provider>();
|
||||
for (final String providerName : providerNames) {
|
||||
if (!providers.containsKey(providerName)) {
|
||||
providers.put(providerName, Network.Provider.getProvider(providerName));
|
||||
providers.put(providerName, _ntwkModel.resolveProvider(providerName));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -400,6 +400,16 @@ public class MockNetworkModelImpl extends ManagerBase implements NetworkModel {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canElementEnableIndividualServicesByName(String providerName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Provider resolveProvider(String providerName) {
|
||||
return Provider.getProvider(providerName);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.cloud.network.NetworkModel#areServicesSupportedInNetwork(long, com.cloud.network.Network.Service[])
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import com.cloud.utils.net.Ip;
|
|||
import com.cloud.vm.Nic;
|
||||
import com.cloud.vm.NicProfile;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class NetworkModelImplTest {
|
||||
|
|
@ -80,6 +81,8 @@ public class NetworkModelImplTest {
|
|||
private NetworkDao _networksDao;
|
||||
@Inject
|
||||
private NetworkOfferingServiceMapDao networkOfferingServiceMapDao;
|
||||
@Mock
|
||||
private ExtensionHelper extensionHelper;
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
|
|
@ -96,6 +99,8 @@ public class NetworkModelImplTest {
|
|||
networkModel._networkOfferingDao = networkOfferingDao;
|
||||
networkModel._ntwkSrvcDao = networkServiceMapDao;
|
||||
networkModel._ntwkOfferingSrvcDao = networkOfferingServiceMapDao;
|
||||
ReflectionTestUtils.setField(networkModel, "extensionHelper", extensionHelper);
|
||||
Mockito.lenient().when(extensionHelper.isNetworkExtensionProvider(Mockito.anyString())).thenReturn(false);
|
||||
}
|
||||
|
||||
private void prepareMocks(boolean isIp6, Network network, DataCenter zone, VpcVO vpc,
|
||||
|
|
@ -242,8 +247,8 @@ public class NetworkModelImplTest {
|
|||
networkOfferingVO.setForVpc(true);
|
||||
Network network = new NetworkVO();
|
||||
List<NetworkServiceMapVO> networkServiceMapVOs = new ArrayList<>();
|
||||
networkServiceMapVOs.add(new NetworkServiceMapVO(15L, Network.Service.Firewall, Network.Provider.VPCVirtualRouter));
|
||||
networkServiceMapVOs.add(new NetworkServiceMapVO(15L, Network.Service.SourceNat, Network.Provider.VPCVirtualRouter));
|
||||
networkServiceMapVOs.add(new NetworkServiceMapVO(15L, Network.Service.Firewall.getName(), Network.Provider.VPCVirtualRouter.getName()));
|
||||
networkServiceMapVOs.add(new NetworkServiceMapVO(15L, Network.Service.SourceNat.getName(), Network.Provider.VPCVirtualRouter.getName()));
|
||||
NetworkElement element = new VpcVirtualRouterElement();
|
||||
|
||||
ReflectionTestUtils.setField(networkModel, "networkElements", List.of(element));
|
||||
|
|
|
|||
|
|
@ -423,6 +423,8 @@ public class AutoScaleManagerImplTest {
|
|||
when(conditionDao.findById(any())).thenReturn(conditionMock);
|
||||
when(conditionDao.persist(any(ConditionVO.class))).thenReturn(conditionMock);
|
||||
|
||||
when(networkModel.resolveProvider(counterProvider)).thenReturn(Network.Provider.VirtualRouter);
|
||||
|
||||
when(accountManager.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
|
||||
Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any());
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ import com.cloud.vm.dao.VMInstanceDao;
|
|||
import com.google.common.collect.Maps;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
||||
|
|
@ -151,6 +153,7 @@ public class ConfigDriveNetworkElementTest {
|
|||
@Mock private CallContext callContextMock;
|
||||
@Mock private DomainVO domainVO;
|
||||
@Mock private NetworkOrchestrationService _networkOrchestrationService;
|
||||
@Mock private ExtensionHelper extensionHelper;
|
||||
|
||||
@Spy @InjectMocks
|
||||
private ConfigDriveNetworkElement _configDrivesNetworkElement = new ConfigDriveNetworkElement();
|
||||
|
|
@ -202,6 +205,8 @@ public class ConfigDriveNetworkElementTest {
|
|||
doReturn(_configDrivesNetworkElement.getProvider().getName()).when(_ntwkSrvcDao).getProviderForServiceInNetwork(NETWORK_ID, Network.Service.UserData);
|
||||
|
||||
_networkModel.setNetworkElements(Arrays.asList(_configDrivesNetworkElement));
|
||||
ReflectionTestUtils.setField(_networkModel, "extensionHelper", extensionHelper);
|
||||
Mockito.lenient().when(extensionHelper.isNetworkExtensionProvider(Mockito.anyString())).thenReturn(false);
|
||||
_networkModel.start();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ import static org.mockito.ArgumentMatchers.anyList;
|
|||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
|
|
@ -92,6 +91,8 @@ public class NetworkACLManagerTest extends TestCase {
|
|||
@Inject
|
||||
NetworkModel _networkModel;
|
||||
@Inject
|
||||
NetworkServiceMapDao networkServiceMapDao;
|
||||
@Inject
|
||||
List<NetworkACLServiceProvider> _networkAclElements;
|
||||
@Inject
|
||||
VpcService _vpcSvc;
|
||||
|
|
@ -169,8 +170,7 @@ public class NetworkACLManagerTest extends TestCase {
|
|||
final List<NetworkVO> networks = new ArrayList<>();
|
||||
networks.add(network);
|
||||
|
||||
NetworkServiceMapDao ntwkSrvcDao = mock(NetworkServiceMapDao.class);
|
||||
when(ntwkSrvcDao.canProviderSupportServiceInNetwork(anyLong(), eq(Network.Service.NetworkACL), nullable(Network.Provider.class))).thenReturn(true);
|
||||
when(networkServiceMapDao.canProviderSupportServiceInNetwork(anyLong(), eq(Network.Service.NetworkACL), nullable(Network.Provider.class))).thenReturn(true);
|
||||
Mockito.when(_networkDao.listByAclId(anyLong())).thenReturn(networks);
|
||||
Mockito.when(_networkDao.findById(anyLong())).thenReturn(network);
|
||||
Mockito.when(networkOfferingDao.isIpv6Supported(anyLong())).thenReturn(false);
|
||||
|
|
@ -277,6 +277,11 @@ public class NetworkACLManagerTest extends TestCase {
|
|||
return Mockito.mock(NetworkModel.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NetworkServiceMapDao networkServiceMapDao() {
|
||||
return Mockito.mock(NetworkServiceMapDao.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public VpcManager vpcManager() {
|
||||
return Mockito.mock(VpcManager.class);
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ import java.util.UUID;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
|
|
@ -247,14 +248,21 @@ public class VpcManagerImplTest {
|
|||
@Test
|
||||
public void getVpcOffSvcProvidersMapForEmptyServiceTest() {
|
||||
long vpcOffId = 1L;
|
||||
VpcOfferingServiceMapVO svcMap = mock(VpcOfferingServiceMapVO.class);
|
||||
Mockito.when(svcMap.getService()).thenReturn(Service.SourceNat.getName());
|
||||
Mockito.when(svcMap.getProvider()).thenReturn(Provider.VPCVirtualRouter.getName());
|
||||
Mockito.when(networkModel.resolveProvider(Provider.VPCVirtualRouter.getName()))
|
||||
.thenReturn(Provider.VPCVirtualRouter);
|
||||
List<VpcOfferingServiceMapVO> list = new ArrayList<VpcOfferingServiceMapVO>();
|
||||
list.add(mock(VpcOfferingServiceMapVO.class));
|
||||
list.add(svcMap);
|
||||
Mockito.when(manager._vpcOffSvcMapDao.listByVpcOffId(vpcOffId)).thenReturn(list);
|
||||
|
||||
Map<Service, Set<Provider>> map = manager.getVpcOffSvcProvidersMap(vpcOffId);
|
||||
|
||||
assertNotNull(map);
|
||||
assertEquals(map.size(), 1);
|
||||
assertTrue(map.containsKey(Service.SourceNat));
|
||||
assertTrue(map.get(Service.SourceNat).contains(Provider.VPCVirtualRouter));
|
||||
}
|
||||
|
||||
protected Map<String, String> createFakeCapabilityInputMap() {
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches
|
|||
* @see com.cloud.network.NetworkService#updatePhysicalNetwork(java.lang.Long, java.lang.String, java.util.List, java.lang.String, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public PhysicalNetwork updatePhysicalNetwork(Long id, String networkSpeed, List<String> tags, String newVnetRangeString, String state) {
|
||||
public PhysicalNetwork updatePhysicalNetwork(Long id, String networkSpeed, List<String> tags, String newVnetRangeString, String state, Map<String, String> externalDetails) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -411,6 +411,15 @@ public class MockNetworkModelImpl extends ManagerBase implements NetworkModel {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Provider resolveProvider(String providerName) {
|
||||
return Provider.getProvider(providerName);
|
||||
}
|
||||
@Override
|
||||
public boolean canElementEnableIndividualServicesByName(String providerName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.cloud.network.NetworkModel#areServicesSupportedInNetwork(long, com.cloud.network.Network.Service[])
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -241,6 +241,7 @@
|
|||
"label.action.unmanage.volume": "Unmanage Volume",
|
||||
"label.action.unmanage.volumes": "Unmanage Volumes",
|
||||
"label.action.unregister.extension.resource": "Unregister extension resource",
|
||||
"label.action.update.extension.resource": "Update extension resource details",
|
||||
"label.action.update.host": "Update Host",
|
||||
"label.action.update.security.groups": "Update security groups",
|
||||
"label.action.update.offering.access": "Update offering access",
|
||||
|
|
@ -4176,6 +4177,8 @@
|
|||
"message.validate.min": "Please enter a value greater than or equal to {0}.",
|
||||
"message.action.delete.object.storage": "Please confirm that you want to delete this Object Store",
|
||||
"message.action.unregister.extension.resource": "Please confirm that you want to unregister extension with this resource",
|
||||
"message.action.update.extension.resource": "Update the extension resource registration details",
|
||||
"message.success.update.extension.resource": "Successfully updated extension resource registration",
|
||||
"message.bgp.peers.null": "Please note, if no BGP peers are selected, the VR will connect to <br> (1) dedicated BGP peers the owner can access, if the owner has dedicated BGP peers and account setting use.system.bgp.peers is set to false; <br> (2) all BGP peers the owner can access, otherwise.<br>",
|
||||
"message.bucket.delete": "Please confirm that you want to delete this Bucket",
|
||||
"migrate.from": "Migrate from",
|
||||
|
|
@ -4238,5 +4241,21 @@
|
|||
"Compute*Month": "Compute * Month",
|
||||
"GB*Month": "GB * Month",
|
||||
"IP*Month": "IP * Month",
|
||||
"Policy*Month": "Policy * Month"
|
||||
"Policy*Month": "Policy * Month",
|
||||
"ExternalNetwork": "External Network",
|
||||
"label.external.network": "External Network",
|
||||
"label.external.network.provider": "External Network Provider",
|
||||
"label.extension": "Extension",
|
||||
"label.services": "Services",
|
||||
"label.add.external.network.provider": "Add External Network Provider",
|
||||
"label.not.added": "Not Added",
|
||||
"label.refresh": "Refresh",
|
||||
"label.run.action": "Run Action",
|
||||
"label.enable.provider": "Enable Provider",
|
||||
"label.disable.provider": "Disable Provider",
|
||||
"label.external.network.service": "External Network Service",
|
||||
"message.confirm.disable.external.network.provider": "Are you sure you want to disable the External Network provider?",
|
||||
"message.no.network.orchestrator.extensions": "No NetworkOrchestrator extensions found. Please create one first via createExtension API.",
|
||||
"message.extension.services.from.capabilities": "Services are derived automatically from the extension's network.capabilities detail.",
|
||||
"message.select.extension": "Please select an extension."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export default {
|
|||
return fields
|
||||
},
|
||||
details: ['name', 'description', 'id', 'type', 'details', 'path', 'pathready', 'isuserdefined', 'orchestratorrequirespreparevm', 'reservedresourcedetails', 'created'],
|
||||
filters: ['orchestrator'],
|
||||
filters: ['orchestrator', 'networkorchestrator'],
|
||||
tabs: [{
|
||||
name: 'details',
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export default {
|
|||
icon: 'edit-outlined',
|
||||
label: 'label.update.physical.network',
|
||||
dataView: true,
|
||||
args: ['vlan', 'tags']
|
||||
args: ['vlan', 'tags', 'externaldetails']
|
||||
},
|
||||
{
|
||||
api: 'addTrafficType',
|
||||
|
|
|
|||
|
|
@ -207,6 +207,20 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'runCustomAction',
|
||||
icon: 'thunderbolt-outlined',
|
||||
label: 'label.run.action',
|
||||
dataView: true,
|
||||
show: (record) => {
|
||||
return 'runCustomAction' in store.getters.apis &&
|
||||
'listCustomActions' in store.getters.apis &&
|
||||
record.service && record.service.some(s =>
|
||||
s.provider && s.provider.some(p => p.name === 'ExternalNetwork'))
|
||||
},
|
||||
popup: true,
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/extension/RunCustomAction.vue')))
|
||||
},
|
||||
{
|
||||
api: 'deleteNetwork',
|
||||
icon: 'delete-outlined',
|
||||
|
|
|
|||
|
|
@ -34,8 +34,25 @@
|
|||
api="listExtensions"
|
||||
:apiParams="extensionsApiParams"
|
||||
resourceType="extension"
|
||||
@change-option="updateResourceTypeByExtension"
|
||||
defaultIcon="appstore-add-outlined" />
|
||||
</a-form-item>
|
||||
<a-form-item name="resourcetype" ref="resourcetype">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.resourcetype')" :tooltip="apiParams.resourcetype.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="form.resourcetype"
|
||||
:placeholder="apiParams.resourcetype.description"
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option v-for="opt in resourceTypeOptions" :key="opt" :label="opt">
|
||||
{{ opt }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="name" ref="name">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
|
||||
|
|
@ -152,6 +169,7 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
roleTypes: [],
|
||||
resourceTypeOptions: ['VirtualMachine', 'Network'],
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
|
|
@ -165,6 +183,7 @@ export default {
|
|||
this.initForm()
|
||||
if (this.extension) {
|
||||
this.form.extensionid = this.extension.id
|
||||
this.updateResourceTypeByExtension(this.extension)
|
||||
}
|
||||
this.roleTypes = this.$fetchCustomActionRoleTypes()
|
||||
},
|
||||
|
|
@ -173,13 +192,30 @@ export default {
|
|||
this.formRef = ref()
|
||||
this.form = reactive({
|
||||
enabled: true,
|
||||
timeout: 5
|
||||
timeout: 5,
|
||||
resourcetype: 'VirtualMachine'
|
||||
})
|
||||
this.rules = reactive({
|
||||
extensionid: [{ required: true, message: `${this.$t('message.error.select')}` }],
|
||||
name: [{ required: true, message: `${this.$t('message.error.name')}` }]
|
||||
name: [{ required: true, message: `${this.$t('message.error.name')}` }],
|
||||
resourcetype: [{ required: true, message: `${this.$t('message.error.select')}` }]
|
||||
})
|
||||
},
|
||||
updateResourceTypeByExtension (selectedExtension) {
|
||||
const type = selectedExtension?.type
|
||||
if (type === 'NetworkOrchestrator') {
|
||||
this.resourceTypeOptions = ['Network']
|
||||
this.form.resourcetype = 'Network'
|
||||
} else if (type === 'Orchestrator') {
|
||||
this.resourceTypeOptions = ['VirtualMachine']
|
||||
this.form.resourcetype = 'VirtualMachine'
|
||||
} else {
|
||||
this.resourceTypeOptions = ['VirtualMachine', 'Network']
|
||||
if (!this.form.resourcetype) {
|
||||
this.form.resourcetype = 'VirtualMachine'
|
||||
}
|
||||
}
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (this.loading) return
|
||||
|
|
@ -189,6 +225,7 @@ export default {
|
|||
const params = {
|
||||
extensionid: values.extensionid || this.extension.id,
|
||||
name: values.name,
|
||||
resourcetype: values.resourcetype,
|
||||
enabled: values.enabled
|
||||
}
|
||||
const keys = ['description', 'allowedroletypes', 'successmessage', 'errormessage', 'timeout']
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export default {
|
|||
},
|
||||
fetchExtensionTypes () {
|
||||
this.extensionTypes = []
|
||||
const extensionTypesList = ['Orchestrator']
|
||||
const extensionTypesList = ['Orchestrator', 'NetworkOrchestrator']
|
||||
extensionTypesList.forEach((item) => {
|
||||
this.extensionTypes.push({
|
||||
id: item,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@
|
|||
{{ text && $toLocaleDate(text) }}
|
||||
</template>
|
||||
<template v-if="column.key === 'actions'">
|
||||
<span style="margin-right: 5px">
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.action.update.extension.resource')"
|
||||
type="default"
|
||||
icon="edit-outlined"
|
||||
@onClick="openUpdateModal(record)" />
|
||||
</span>
|
||||
<span style="margin-right: 5px">
|
||||
<a-popconfirm
|
||||
v-if="'unregisterExtension' in $store.getters.apis"
|
||||
|
|
@ -59,6 +66,20 @@
|
|||
:data-map="record.details" />
|
||||
</template>
|
||||
</a-table>
|
||||
<a-modal
|
||||
v-if="updateModalVisible"
|
||||
:visible="updateModalVisible"
|
||||
:width="600"
|
||||
:title="$t('label.action.update.extension.resource')"
|
||||
:closable="true"
|
||||
:footer="null"
|
||||
@cancel="closeUpdateModal">
|
||||
<update-registered-extension
|
||||
:resource="resource"
|
||||
:extension-resource="selectedResource"
|
||||
@refresh-data="handleRefreshData"
|
||||
@close-action="closeUpdateModal" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -67,13 +88,16 @@ import { postAPI } from '@/api'
|
|||
import eventBus from '@/config/eventBus'
|
||||
import ObjectListTable from '@/components/view/ObjectListTable.vue'
|
||||
import TooltipButton from '@/components/widgets/TooltipButton'
|
||||
import UpdateRegisteredExtension from '@/views/extension/UpdateRegisteredExtension'
|
||||
|
||||
export default {
|
||||
name: 'ExtensionResourcesTab',
|
||||
components: {
|
||||
ObjectListTable,
|
||||
TooltipButton
|
||||
TooltipButton,
|
||||
UpdateRegisteredExtension
|
||||
},
|
||||
inject: ['parentFetchData'],
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
@ -103,7 +127,9 @@ export default {
|
|||
title: this.$t('label.actions')
|
||||
}
|
||||
],
|
||||
unregisterLoading: false
|
||||
unregisterLoading: false,
|
||||
updateModalVisible: false,
|
||||
selectedResource: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -112,13 +138,27 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
openUpdateModal (record) {
|
||||
this.selectedResource = record
|
||||
this.updateModalVisible = true
|
||||
},
|
||||
closeUpdateModal () {
|
||||
this.updateModalVisible = false
|
||||
this.selectedResource = null
|
||||
},
|
||||
handleRefreshData () {
|
||||
if (this.parentFetchData) {
|
||||
this.parentFetchData()
|
||||
}
|
||||
},
|
||||
unregisterExtension (record) {
|
||||
const params = {
|
||||
extensionid: this.resource.id,
|
||||
resourceid: record.id,
|
||||
resourcetype: record.type
|
||||
}
|
||||
postAPI('unregisterExtension', params).then(json => {
|
||||
this.unregisterLoading = true
|
||||
postAPI('unregisterExtension', params).then(() => {
|
||||
eventBus.emit('async-job-complete', null)
|
||||
this.$notification.success({
|
||||
message: this.$t('label.unregister.extension'),
|
||||
|
|
@ -127,7 +167,7 @@ export default {
|
|||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
this.deleteLoading = false
|
||||
this.unregisterLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export default {
|
|||
},
|
||||
fetchExtensionResourceTypes () {
|
||||
this.resourceTypes = []
|
||||
const resourceTypesList = ['Cluster']
|
||||
const resourceTypesList = ['Cluster', 'PhysicalNetwork']
|
||||
resourceTypesList.forEach((item) => {
|
||||
this.resourceTypes.push({
|
||||
id: item,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
<template>
|
||||
<div class="form-layout" v-ctrl-enter="handleSubmit">
|
||||
<a-form
|
||||
:ref="formRef"
|
||||
:model="form"
|
||||
:loading="loading"
|
||||
layout="vertical"
|
||||
@finish="handleSubmit">
|
||||
<a-form-item name="details" ref="details">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.details')" :tooltip="$t('message.add.extension.resource.details')"/>
|
||||
</template>
|
||||
<div style="margin-bottom: 10px">{{ $t('message.add.extension.resource.details') }}</div>
|
||||
<details-input
|
||||
v-model:value="form.details" />
|
||||
</a-form-item>
|
||||
<div :span="24" class="action-button">
|
||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, toRaw } from 'vue'
|
||||
import { postAPI } from '@/api'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import DetailsInput from '@/components/widgets/DetailsInput'
|
||||
|
||||
export default {
|
||||
name: 'UpdateRegisteredExtension',
|
||||
components: {
|
||||
TooltipLabel,
|
||||
DetailsInput
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
extensionResource: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initForm()
|
||||
},
|
||||
methods: {
|
||||
initForm () {
|
||||
this.formRef = ref()
|
||||
this.form = reactive({
|
||||
details: this.extensionResource.details ? { ...this.extensionResource.details } : {}
|
||||
})
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (this.loading) return
|
||||
this.formRef.value.validate().then(() => {
|
||||
const values = toRaw(this.form)
|
||||
this.loading = true
|
||||
const params = {
|
||||
extensionid: this.resource.id,
|
||||
resourceid: this.extensionResource.id,
|
||||
resourcetype: this.extensionResource.type
|
||||
}
|
||||
if (values.details && Object.keys(values.details).length > 0) {
|
||||
Object.entries(values.details).forEach(([key, value]) => {
|
||||
params['details[0].' + key] = value
|
||||
})
|
||||
} else {
|
||||
params.cleanupdetails = true
|
||||
}
|
||||
postAPI('updateRegisteredExtension', params).then(() => {
|
||||
this.$emit('refresh-data')
|
||||
this.$notification.success({
|
||||
message: this.$t('label.action.update.extension.resource'),
|
||||
description: this.$t('message.success.update.extension.resource')
|
||||
})
|
||||
this.closeAction()
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: this.$t('message.request.failed'),
|
||||
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
|
||||
duration: 0
|
||||
})
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
})
|
||||
},
|
||||
closeAction () {
|
||||
this.$emit('close-action')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.form-layout {
|
||||
width: 80vw;
|
||||
@media (min-width: 600px) {
|
||||
width: 550px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -18,10 +18,21 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-spin :spinning="fetchLoading">
|
||||
<!-- Add Extension Provider button: only shown when extension is registered but NSP not yet created -->
|
||||
<a-button
|
||||
v-if="isExtensionTab && !nsps[tabKey]"
|
||||
:disabled="!('addNetworkServiceProvider' in $store.getters.apis)"
|
||||
type="dashed"
|
||||
style="width: 100%; margin-bottom: 12px;"
|
||||
@click="handleAddExternalNetworkProvider">
|
||||
<template #icon><plus-outlined /></template>
|
||||
{{ $t('label.add.external.network.provider') }}
|
||||
</a-button>
|
||||
<a-tabs
|
||||
:tabPosition="device === 'mobile' ? 'top' : 'left'"
|
||||
:animated="false"
|
||||
@change="onTabChange">
|
||||
<!-- Hardcoded NSP tabs -->
|
||||
<a-tab-pane
|
||||
class="custom-tab-pane"
|
||||
v-for="item in hardcodedNsps"
|
||||
|
|
@ -41,8 +52,67 @@
|
|||
:zoneId="resource.zoneid"
|
||||
:tabKey="tabKey"/>
|
||||
</a-tab-pane>
|
||||
<!-- Dynamic extension-based provider tabs (one per registered NetworkOrchestrator extension) -->
|
||||
<a-tab-pane
|
||||
class="custom-tab-pane"
|
||||
v-for="ext in registeredExtensions"
|
||||
:key="ext.name">
|
||||
<template #tab>
|
||||
<span>
|
||||
{{ ext.name }}
|
||||
<status :text="nsps[ext.name] ? nsps[ext.name].state : $t('label.not.added')" style="margin-bottom: 6px; margin-left: 6px" />
|
||||
</span>
|
||||
</template>
|
||||
<provider-item
|
||||
v-if="tabKey === ext.name"
|
||||
:loading="loading"
|
||||
:itemNsp="extensionNspItem(ext.name)"
|
||||
:nsp="nsps[ext.name]"
|
||||
:resourceId="resource.id"
|
||||
:zoneId="resource.zoneid"
|
||||
:tabKey="tabKey"/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-spin>
|
||||
|
||||
<!-- Add External Network Provider modal: selects extension (services come from extension capabilities) -->
|
||||
<a-modal
|
||||
:visible="showAddExtNetProviderModal"
|
||||
:title="$t('label.add.external.network.provider')"
|
||||
:maskClosable="false"
|
||||
:footer="null"
|
||||
@cancel="showAddExtNetProviderModal = false">
|
||||
<a-spin :spinning="extensionProviderLoading">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item :label="$t('label.extension')">
|
||||
<a-select
|
||||
v-model:value="extNetProviderForm.extensionId"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0">
|
||||
<a-select-option
|
||||
v-for="ext in availableExtensions"
|
||||
:key="ext.id"
|
||||
:value="ext.id"
|
||||
:label="ext.name">
|
||||
{{ ext.name }} <span style="color: #aaa">({{ ext.state }})</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<div v-if="availableExtensions.length === 0" style="color: #faad14; margin-top: 4px;">
|
||||
{{ $t('message.no.network.orchestrator.extensions') }}
|
||||
</div>
|
||||
<div v-else style="color: #8c8c8c; font-size: 12px; margin-top: 4px;">
|
||||
{{ $t('message.extension.services.from.capabilities') }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
<div class="action-button">
|
||||
<a-button @click="showAddExtNetProviderModal = false">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button type="primary" :disabled="!extNetProviderForm.extensionId" @click="handleAddExtNetProvider">{{ $t('label.ok') }}</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
|
||||
<div v-if="showFormAction">
|
||||
<keep-alive v-if="currentAction.component">
|
||||
<a-modal
|
||||
|
|
@ -170,10 +240,20 @@ export default {
|
|||
actionLoading: false,
|
||||
showFormAction: false,
|
||||
currentAction: {},
|
||||
tabKey: 'BaremetalDhcpProvider'
|
||||
tabKey: 'BaremetalDhcpProvider',
|
||||
showAddExtNetProviderModal: false,
|
||||
extensionProviderLoading: false,
|
||||
availableExtensions: [],
|
||||
extNetProviderForm: {
|
||||
extensionId: null
|
||||
},
|
||||
registeredExtensions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isExtensionTab () {
|
||||
return this.registeredExtensions.some(ext => ext.name === this.tabKey)
|
||||
},
|
||||
hardcodedNsps () {
|
||||
return [
|
||||
{
|
||||
|
|
@ -848,7 +928,7 @@ export default {
|
|||
listView: true,
|
||||
label: 'label.enable.provider',
|
||||
confirm: 'message.confirm.enable.provider',
|
||||
show: (record) => { return (record && record.id && record.state === 'Disabled') },
|
||||
show: (record) => { return record && record.id && record.state === 'Disabled' },
|
||||
mapping: {
|
||||
state: {
|
||||
value: (record) => { return 'Enabled' }
|
||||
|
|
@ -1143,11 +1223,106 @@ export default {
|
|||
this.form = reactive({})
|
||||
this.rules = reactive({})
|
||||
},
|
||||
handleAddExternalNetworkProvider () {
|
||||
// Open the extension picker modal — services come from extension capabilities
|
||||
this.extNetProviderForm = { extensionId: null }
|
||||
this.extensionProviderLoading = true
|
||||
this.showAddExtNetProviderModal = true
|
||||
getAPI('listExtensions', { type: 'NetworkOrchestrator' }).then(json => {
|
||||
this.availableExtensions = (json.listextensionsresponse && json.listextensionsresponse.extension) || []
|
||||
if (this.availableExtensions.length > 0) {
|
||||
this.extNetProviderForm.extensionId = this.availableExtensions[0].id
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
this.extensionProviderLoading = false
|
||||
})
|
||||
},
|
||||
_updateServicesFromExtension (extensionId) {
|
||||
// No longer needed — services are derived from extension capabilities server-side
|
||||
},
|
||||
async handleAddExtNetProvider () {
|
||||
if (this.extensionProviderLoading) return
|
||||
const extensionId = this.extNetProviderForm.extensionId
|
||||
if (!extensionId) {
|
||||
this.$message.error(this.$t('message.select.extension'))
|
||||
return
|
||||
}
|
||||
const ext = this.availableExtensions.find(e => e.id === extensionId)
|
||||
const extName = ext ? ext.name : ''
|
||||
if (!extName) {
|
||||
this.$message.error(this.$t('message.select.extension'))
|
||||
return
|
||||
}
|
||||
|
||||
this.extensionProviderLoading = true
|
||||
try {
|
||||
// registerExtension auto-creates the NSP (Enabled) with services from network.capabilities
|
||||
await postAPI('registerExtension', {
|
||||
extensionid: extensionId,
|
||||
resourceid: this.resource.id,
|
||||
resourcetype: 'PhysicalNetwork'
|
||||
})
|
||||
this.$message.success(this.$t('label.add.external.network.provider') + ': ' + extName)
|
||||
this.showAddExtNetProviderModal = false
|
||||
this.fetchData()
|
||||
} catch (error) {
|
||||
this.$notifyError(error)
|
||||
} finally {
|
||||
this.extensionProviderLoading = false
|
||||
}
|
||||
},
|
||||
fetchData () {
|
||||
if (!this.resource || !('id' in this.resource)) {
|
||||
return
|
||||
}
|
||||
this.fetchServiceProvider()
|
||||
this.fetchRegisteredExtensions()
|
||||
},
|
||||
fetchRegisteredExtensions () {
|
||||
// Load NetworkOrchestrator extensions registered to this physical network
|
||||
getAPI('listExtensions', {
|
||||
type: 'NetworkOrchestrator',
|
||||
resourceid: this.resource.id,
|
||||
resourcetype: 'PhysicalNetwork'
|
||||
}).then(json => {
|
||||
this.registeredExtensions = (json.listextensionsresponse && json.listextensionsresponse.extension) || []
|
||||
}).catch(() => {
|
||||
this.registeredExtensions = []
|
||||
})
|
||||
},
|
||||
extensionNspItem (extName) {
|
||||
// Build a ProviderItem-compatible itemNsp descriptor for extension-backed NSPs.
|
||||
// Mirrors the structure of hardcoded entries in hardcodedNsps.
|
||||
return {
|
||||
title: extName,
|
||||
details: ['name', 'state', 'id', 'physicalnetworkid', 'servicelist'],
|
||||
actions: [
|
||||
{
|
||||
api: 'updateNetworkServiceProvider',
|
||||
icon: 'play-circle-outlined',
|
||||
listView: true,
|
||||
label: 'label.enable.provider',
|
||||
confirm: 'message.confirm.enable.provider',
|
||||
show: (record) => record && record.id && record.state === 'Disabled',
|
||||
mapping: {
|
||||
state: { value: () => 'Enabled' }
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'updateNetworkServiceProvider',
|
||||
icon: 'stop-outlined',
|
||||
listView: true,
|
||||
label: 'label.disable.provider',
|
||||
confirm: 'message.confirm.disable.provider',
|
||||
show: (record) => record && record.id && record.state === 'Enabled',
|
||||
mapping: {
|
||||
state: { value: () => 'Disabled' }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
fetchServiceProvider (name) {
|
||||
this.fetchLoading = true
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
:loading="loading"
|
||||
:columns="listCols"
|
||||
:dataSource="dataSource"
|
||||
:rowKey="record => record.id || record.name || record.nvpdeviceid || record.resourceid"
|
||||
:rowKey="record => record.id || record.name || record.nvpdeviceid || record.resourceid || record.physicalnetworkid"
|
||||
:pagination="false"
|
||||
:scroll="scrollable">
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
|
|
@ -67,6 +67,7 @@
|
|||
<span v-else-if="resource.name==='CiscoVnmc' && title==='listCiscoAsa1000vResources'">
|
||||
{{ $t('label.delete.ciscoasa1000v') }}
|
||||
</span>
|
||||
<!-- External network device UI removed: use extension registration details instead -->
|
||||
</template>
|
||||
<tooltip-button
|
||||
v-if="resource.name==='Ovs'"
|
||||
|
|
@ -86,6 +87,12 @@
|
|||
@onClick="onDelete(record)"/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'details'">
|
||||
<span v-if="text && typeof text === 'object'">
|
||||
<a-tag v-for="(val, key) in text" :key="key" style="margin-bottom: 2px;">{{ key }}: {{ val }}</a-tag>
|
||||
</span>
|
||||
<span v-else>{{ text }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'lbdevicestate'">
|
||||
<status :text="text ? text : ''" displayText />
|
||||
</template>
|
||||
|
|
@ -269,6 +276,7 @@ export default {
|
|||
name = record.hostname
|
||||
params.resourceid = record.resourceid
|
||||
break
|
||||
// ExternalNetwork provider action removed; use extension registration/unregister instead
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,10 +60,10 @@
|
|||
<a-radio-button value="isolated">
|
||||
{{ $t('label.isolated') }}
|
||||
</a-radio-button>
|
||||
<a-radio-button value="l2" v-if="form.provider !== 'NSX' && form.provider !== 'Netris'">
|
||||
<a-radio-button value="l2" v-if="form.provider !== 'NSX' && form.provider !== 'Netris' && !isExternalNetworkProvider">
|
||||
{{ $t('label.l2') }}
|
||||
</a-radio-button>
|
||||
<a-radio-button value="shared" v-if="form.provider !== 'NSX' && form.provider !== 'Netris'">
|
||||
<a-radio-button value="shared" v-if="form.provider !== 'NSX' && form.provider !== 'Netris' && !isExternalNetworkProvider">
|
||||
{{ $t('label.shared') }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
|
|
@ -138,7 +138,16 @@
|
|||
<a-select-option key="" >{{ }}</a-select-option>
|
||||
<a-select-option :value="'NSX'" :label="$t('label.nsx')"> {{ $t('label.nsx') }} </a-select-option>
|
||||
<a-select-option :value="'Netris'" :label="$t('label.netris')"> {{ $t('label.netris') }} </a-select-option>
|
||||
</a-select>
|
||||
<!-- Dynamic extension-based providers registered to guest physical networks.
|
||||
The value is the extension/NSP name so the provider is correctly resolved. -->
|
||||
<a-select-option
|
||||
v-for="ext in availableExtensionProviders"
|
||||
:key="ext.name"
|
||||
:value="ext.name"
|
||||
:label="ext.name">
|
||||
{{ ext.name }} <span style="color: #aaa">({{ $t('label.external.network.provider') }})</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
|
@ -208,7 +217,7 @@
|
|||
</a-form-item>
|
||||
<a-row :gutter="12">
|
||||
<a-col :md="12" :lg="12">
|
||||
<a-form-item name="promiscuousmode" ref="promiscuousmode" v-if="form.provider !== 'NSX' && form.provider !== 'Netris'">
|
||||
<a-form-item name="promiscuousmode" ref="promiscuousmode" v-if="form.provider !== 'NSX' && form.provider !== 'Netris' && !isExternalNetworkProvider">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.promiscuousmode')" :tooltip="$t('message.network.offering.promiscuous.mode')"/>
|
||||
</template>
|
||||
|
|
@ -303,8 +312,8 @@
|
|||
<CheckBoxSelectPair
|
||||
:resourceKey="item.name"
|
||||
:checkBoxLabel="item.description"
|
||||
:forExternalNetProvider="form.provider === 'NSX' || form.provider === 'Netris'"
|
||||
:defaultCheckBoxValue="form.provider === 'NSX' || form.provider === 'Netris'"
|
||||
:forExternalNetProvider="form.provider === 'NSX' || form.provider === 'Netris' || isExternalNetworkProvider"
|
||||
:defaultCheckBoxValue="form.provider === 'NSX' || form.provider === 'Netris' || isExternalNetworkProvider"
|
||||
:selectOptions="!supportedServiceLoading ? item.provider: []"
|
||||
@handle-checkselectpair-change="handleSupportedServiceChange"/>
|
||||
</a-list-item>
|
||||
|
|
@ -669,8 +678,20 @@ export default {
|
|||
description: 'Netris',
|
||||
enabled: true
|
||||
},
|
||||
externalNetworkProviderObj: {
|
||||
name: '',
|
||||
description: 'External Network',
|
||||
enabled: true
|
||||
},
|
||||
nsxSupportedServicesMap: {},
|
||||
netrisSupportedServicesMap: {}
|
||||
netrisSupportedServicesMap: {},
|
||||
externalNetworkSupportedServicesMap: {},
|
||||
availableExtensionProviders: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isExternalNetworkProvider () {
|
||||
return this.availableExtensionProviders.some(e => e.name === this.provider)
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
|
|
@ -732,6 +753,30 @@ export default {
|
|||
this.fetchServiceOfferingData()
|
||||
this.fetchIpv6NetworkOfferingConfiguration()
|
||||
this.fetchRoutedNetworkConfiguration()
|
||||
this.fetchExtensionProviders()
|
||||
},
|
||||
fetchExtensionProviders () {
|
||||
// Load NetworkOrchestrator extensions that are registered to at least one
|
||||
// physical network (i.e. have a corresponding NetworkServiceProvider entry).
|
||||
// Only these can be selected as a provider when creating a network offering.
|
||||
getAPI('listExtensions', { type: 'NetworkOrchestrator', state: 'Enabled' }).then(json => {
|
||||
const allExts = (json.listextensionsresponse && json.listextensionsresponse.extension) || []
|
||||
if (allExts.length === 0) {
|
||||
this.availableExtensionProviders = []
|
||||
return
|
||||
}
|
||||
// Filter to those which have at least one matching NSP (nsp name == extension name)
|
||||
getAPI('listNetworkServiceProviders', {}).then(nspJson => {
|
||||
const nsps = (nspJson.listnetworkserviceprovidersresponse && nspJson.listnetworkserviceprovidersresponse.networkserviceprovider) || []
|
||||
const nspNames = new Set(nsps.map(n => n.name))
|
||||
this.availableExtensionProviders = allExts.filter(e => nspNames.has(e.name))
|
||||
}).catch(() => {
|
||||
// Fallback: show all enabled extensions
|
||||
this.availableExtensionProviders = allExts
|
||||
})
|
||||
}).catch(() => {
|
||||
this.availableExtensionProviders = []
|
||||
})
|
||||
},
|
||||
isAdmin () {
|
||||
return isAdmin()
|
||||
|
|
@ -906,7 +951,7 @@ export default {
|
|||
this.supportedServiceLoading = true
|
||||
var supportedServices = this.supportedServices
|
||||
var self = this
|
||||
if (this.provider !== 'NSX' && this.provider !== 'Netris') {
|
||||
if (this.provider !== 'NSX' && this.provider !== 'Netris' && !this.isExternalNetworkProvider) {
|
||||
if (this.networkmode === 'ROUTED' && this.guestType === 'isolated') {
|
||||
supportedServices = supportedServices.filter(service => {
|
||||
return !['SourceNat', 'StaticNat', 'Lb', 'PortForwarding', 'Vpn'].includes(service.name)
|
||||
|
|
@ -943,6 +988,8 @@ export default {
|
|||
return Object.keys(this.nsxSupportedServicesMap).includes(svc.name)
|
||||
} else if (this.provider === 'Netris') {
|
||||
return Object.keys(this.netrisSupportedServicesMap).includes(svc.name)
|
||||
} else if (this.isExternalNetworkProvider) {
|
||||
return Object.keys(this.externalNetworkSupportedServicesMap).includes(svc.name)
|
||||
}
|
||||
})
|
||||
supportedServices = supportedServices.map(svc => {
|
||||
|
|
@ -951,6 +998,8 @@ export default {
|
|||
svc.provider = [this.NSX]
|
||||
} else if (this.provider === 'Netris') {
|
||||
svc.provider = [this.Netris]
|
||||
} else if (this.isExternalNetworkProvider) {
|
||||
svc.provider = [this.externalNetworkProviderObj]
|
||||
}
|
||||
} else {
|
||||
if (this.forVpc) {
|
||||
|
|
@ -1029,9 +1078,46 @@ export default {
|
|||
...(this.forVpc && { NetworkACL: this.Netris }),
|
||||
...(!this.forVpc && { Firewall: this.Netris })
|
||||
}
|
||||
} else if (this.isExternalNetworkProvider) {
|
||||
// Extension-backed provider: services come from the extension's network.capabilities.
|
||||
// this.provider is the extension name (= NSP name)
|
||||
const extProviderObj = {
|
||||
name: this.provider,
|
||||
description: this.provider,
|
||||
enabled: true
|
||||
}
|
||||
const svcMap = { Dhcp: this.VR, Dns: this.VR, UserData: this.VR }
|
||||
// Infer services from the selected extension's network.capabilities detail
|
||||
const extDef = this.availableExtensionProviders.find(e => e.name === this.provider)
|
||||
const services = this._getExtensionServices(extDef)
|
||||
if (services.length > 0) {
|
||||
services.forEach(svc => {
|
||||
if (!['Dhcp', 'Dns', 'UserData'].includes(svc)) {
|
||||
svcMap[svc] = extProviderObj
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Default services if no capabilities declared
|
||||
svcMap.SourceNat = extProviderObj
|
||||
svcMap.StaticNat = extProviderObj
|
||||
svcMap.PortForwarding = extProviderObj
|
||||
svcMap.Firewall = extProviderObj
|
||||
svcMap.Gateway = extProviderObj
|
||||
}
|
||||
this.externalNetworkSupportedServicesMap = svcMap
|
||||
this.externalNetworkProviderObj = extProviderObj
|
||||
}
|
||||
this.fetchSupportedServiceData()
|
||||
},
|
||||
_getExtensionServices (extDef) {
|
||||
if (!extDef || !extDef.details || !extDef.details['network.capabilities']) return []
|
||||
try {
|
||||
const caps = JSON.parse(extDef.details['network.capabilities'])
|
||||
return (caps && caps.services) ? caps.services : []
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
handleForNetworkModeChange (networkMode) {
|
||||
this.networkmode = networkMode
|
||||
this.fetchSupportedServiceData()
|
||||
|
|
|
|||
|
|
@ -86,6 +86,15 @@
|
|||
<a-select-option key="" >{{ }}</a-select-option>
|
||||
<a-select-option :value="'NSX'" :label="$t('label.nsx')"> {{ $t('label.nsx') }} </a-select-option>
|
||||
<a-select-option :value="'Netris'" :label="$t('label.netris')"> {{ $t('label.netris') }} </a-select-option>
|
||||
<!-- Dynamic extension-based providers registered to guest physical networks.
|
||||
The value is the extension/NSP name for correct provider resolution. -->
|
||||
<a-select-option
|
||||
v-for="ext in availableExtensionProviders"
|
||||
:key="ext.name"
|
||||
:value="ext.name"
|
||||
:label="ext.name">
|
||||
{{ ext.name }} <span style="color: #aaa">({{ $t('label.external.network.provider') }})</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
|
@ -152,8 +161,8 @@
|
|||
<CheckBoxSelectPair
|
||||
:resourceKey="item.name"
|
||||
:checkBoxLabel="item.description"
|
||||
:forExternalNetProvider="form.provider === 'NSX' || form.provider === 'Netris'"
|
||||
:defaultCheckBoxValue="form.provider === 'NSX' || form.provider === 'Netris'"
|
||||
:forExternalNetProvider="form.provider === 'NSX' || form.provider === 'Netris' || isExternalNetworkProvider"
|
||||
:defaultCheckBoxValue="form.provider === 'NSX' || form.provider === 'Netris' || isExternalNetworkProvider"
|
||||
:selectOptions="item.provider"
|
||||
@handle-checkselectpair-change="handleSupportedServiceChange"/>
|
||||
</a-list-item>
|
||||
|
|
@ -337,9 +346,22 @@ export default {
|
|||
enabled: true
|
||||
},
|
||||
nsxSupportedServicesMap: {},
|
||||
externalNetworkProviderObj: {
|
||||
name: '',
|
||||
description: 'External Network',
|
||||
enabled: true
|
||||
},
|
||||
externalNetworkSupportedServicesMap: {},
|
||||
availableExtensionProviders: [],
|
||||
conservemode: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isExternalNetworkProvider () {
|
||||
const selectedProvider = this.form?.provider || this.provider
|
||||
return this.availableExtensionProviders.some(e => e.name === selectedProvider)
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.apiParams = this.$getApiParams('createVPCOffering')
|
||||
},
|
||||
|
|
@ -384,6 +406,25 @@ export default {
|
|||
this.fetchSupportedServiceData()
|
||||
this.fetchIpv6NetworkOfferingConfiguration()
|
||||
this.fetchRoutedNetworkConfiguration()
|
||||
this.fetchExtensionProviders()
|
||||
},
|
||||
fetchExtensionProviders () {
|
||||
getAPI('listExtensions', { type: 'NetworkOrchestrator', state: 'Enabled' }).then(json => {
|
||||
const allExts = (json.listextensionsresponse && json.listextensionsresponse.extension) || []
|
||||
if (allExts.length === 0) {
|
||||
this.availableExtensionProviders = []
|
||||
return
|
||||
}
|
||||
getAPI('listNetworkServiceProviders', {}).then(nspJson => {
|
||||
const nsps = (nspJson.listnetworkserviceprovidersresponse && nspJson.listnetworkserviceprovidersresponse.networkserviceprovider) || []
|
||||
const nspNames = new Set(nsps.map(n => n.name))
|
||||
this.availableExtensionProviders = allExts.filter(e => nspNames.has(e.name))
|
||||
}).catch(() => {
|
||||
this.availableExtensionProviders = allExts
|
||||
})
|
||||
}).catch(() => {
|
||||
this.availableExtensionProviders = []
|
||||
})
|
||||
},
|
||||
isAdmin () {
|
||||
return isAdmin()
|
||||
|
|
@ -433,7 +474,18 @@ export default {
|
|||
},
|
||||
fetchSupportedServiceData () {
|
||||
var services = []
|
||||
if (this.provider === 'NSX') {
|
||||
if (this.isExternalNetworkProvider) {
|
||||
const serviceMap = this._buildExternalVpcServiceMap()
|
||||
Object.keys(serviceMap).forEach(serviceName => {
|
||||
services.push({
|
||||
name: serviceName,
|
||||
enabled: true,
|
||||
provider: Array.isArray(serviceMap[serviceName])
|
||||
? serviceMap[serviceName]
|
||||
: [serviceMap[serviceName]]
|
||||
})
|
||||
})
|
||||
} else if (this.provider === 'NSX') {
|
||||
services.push({
|
||||
name: 'Dhcp',
|
||||
enabled: true,
|
||||
|
|
@ -624,9 +676,76 @@ export default {
|
|||
if (this.provider === 'NSX') {
|
||||
this.form.nsxsupportlb = true
|
||||
this.handleNsxLbService(true)
|
||||
} else if (this.isExternalNetworkProvider) {
|
||||
this._buildExternalVpcServiceMap()
|
||||
}
|
||||
this.fetchSupportedServiceData()
|
||||
},
|
||||
_getExtensionServices (extDef) {
|
||||
if (!extDef || !extDef.details) {
|
||||
return []
|
||||
}
|
||||
|
||||
const capsJson = extDef.details['network.capabilities']
|
||||
if (capsJson) {
|
||||
try {
|
||||
const caps = JSON.parse(capsJson)
|
||||
if (caps && Array.isArray(caps.services)) {
|
||||
return caps.services
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore malformed capabilities and fallback to network.services.
|
||||
}
|
||||
}
|
||||
|
||||
const servicesCsv = extDef.details['network.services']
|
||||
if (servicesCsv && typeof servicesCsv === 'string') {
|
||||
return servicesCsv.split(',').map(x => x.trim()).filter(x => x.length > 0)
|
||||
}
|
||||
return []
|
||||
},
|
||||
_buildExternalVpcServiceMap () {
|
||||
const selectedProvider = this.form?.provider || this.provider
|
||||
const extProviderObj = {
|
||||
name: selectedProvider,
|
||||
description: selectedProvider,
|
||||
enabled: true
|
||||
}
|
||||
const extWithFallbackProviders = [
|
||||
{ name: selectedProvider },
|
||||
{ name: 'VpcVirtualRouter' },
|
||||
{ name: 'ConfigDrive' }
|
||||
]
|
||||
const serviceMap = {
|
||||
Dhcp: extWithFallbackProviders,
|
||||
Dns: extWithFallbackProviders,
|
||||
UserData: extWithFallbackProviders
|
||||
}
|
||||
|
||||
const extDef = this.availableExtensionProviders.find(e => e.name === selectedProvider)
|
||||
const services = this._getExtensionServices(extDef)
|
||||
const allowedVpcServices = new Set([
|
||||
'Gateway', 'Lb', 'StaticNat', 'SourceNat', 'NetworkACL', 'PortForwarding', 'Vpn'
|
||||
])
|
||||
|
||||
services.forEach(service => {
|
||||
if (allowedVpcServices.has(service)) {
|
||||
serviceMap[service] = [{ name: selectedProvider }]
|
||||
}
|
||||
})
|
||||
|
||||
// Fallback for older extensions that only declare partial details.
|
||||
if (Object.keys(serviceMap).length <= 3) {
|
||||
serviceMap.SourceNat = [{ name: selectedProvider }]
|
||||
serviceMap.StaticNat = [{ name: selectedProvider }]
|
||||
serviceMap.PortForwarding = [{ name: selectedProvider }]
|
||||
serviceMap.NetworkACL = [{ name: selectedProvider }]
|
||||
}
|
||||
|
||||
this.externalNetworkProviderObj = extProviderObj
|
||||
this.externalNetworkSupportedServicesMap = serviceMap
|
||||
return serviceMap
|
||||
},
|
||||
handleNsxLbService (supportLb) {
|
||||
console.log(supportLb)
|
||||
if (!supportLb) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue