This commit is contained in:
Wei Zhou 2026-05-12 11:16:31 +02:00 committed by GitHub
commit 5cf44714fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
75 changed files with 11039 additions and 332 deletions

View File

@ -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";

View File

@ -116,6 +116,7 @@ public interface Network extends ControlledEntity, StateObject<Network.State>, I
public static final Service NetworkACL = new Service("NetworkACL", Capability.SupportedProtocols);
public static final Service Connectivity = new Service("Connectivity", Capability.DistributedRouter, Capability.RegionLevelVpc, Capability.StretchedL2Subnet,
Capability.NoVlan, Capability.PublicAccess);
public static final Service CustomAction = new Service("CustomAction");
private final String name;
private final Capability[] caps;
@ -207,6 +208,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 +252,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 {

View File

@ -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

View File

@ -81,7 +81,11 @@ public class Networks {
return uri == null ? null : uri.getAuthority();
}
},
Vswitch("vs", String.class), LinkLocal(null, null), Vnet("vnet", Long.class), Storage("storage", Integer.class), Lswitch("lswitch", String.class) {
Vswitch("vs", String.class),
LinkLocal(null, null),
Vnet("vnet", Long.class),
Storage("storage", Integer.class),
Lswitch("lswitch", String.class) {
@Override
public <T> URI toUri(T value) {
try {
@ -99,7 +103,8 @@ public class Networks {
return uri == null ? null : uri.getSchemeSpecificPart();
}
},
Mido("mido", String.class), Pvlan("pvlan", String.class),
Mido("mido", String.class),
Pvlan("pvlan", String.class),
Vxlan("vxlan", Long.class) {
@Override
public <T> URI toUri(T value) {

View File

@ -146,4 +146,8 @@ public interface NetworkElement extends Adapter {
* @return true/false
*/
boolean verifyServicesCombination(Set<Service> services);
default boolean rollingRestartSupported() {
return true;
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -48,7 +48,9 @@ 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),
Vpc(com.cloud.network.vpc.Vpc.class);
private final Class<?> clazz;

View File

@ -18,10 +18,120 @@
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";
/**
* Detail key used by an OVS-backed NetworkOrchestrator extension to declare
* how its Logical Switch Port name should be matched against the OVS
* {@code external_ids:iface-id} written by libvirt on the hypervisor.
*
* <p>Currently supported value:</p>
* <ul>
* <li>{@code "lswitch"} the framework sets {@code BroadcastDomainType.Lswitch}
* on the {@link com.cloud.vm.NicProfile} during {@code prepare(...)} and
* propagates {@code nic.getUuid()} to per-NIC script commands as
* {@code --nic-uuid}. The extension is then expected to use that UUID as
* the LSP name, so it matches the {@code interfaceid} that
* {@code OvsVifDriver} emits in the libvirt {@code <virtualport>} for
* {@code Lswitch} broadcast type.</li>
* </ul>
*
* <p>If absent, the framework keeps the network's broadcast type unchanged
* (typically {@code Vlan}) and does not propagate {@code --nic-uuid}.</p>
*/
String VIF_BINDING_DETAIL_KEY = "vif.binding";
/** Value of {@link #VIF_BINDING_DETAIL_KEY} that selects the Lswitch path. */
String VIF_BINDING_LSWITCH = "lswitch";
String getExtensionScriptPath(Extension extension);
/**
* 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);
}

View File

@ -24,7 +24,8 @@ import org.apache.cloudstack.api.InternalIdentity;
public interface ExtensionResourceMap extends InternalIdentity, Identity {
enum ResourceType {
Cluster
Cluster,
PhysicalNetwork
}
long getExtensionId();

View File

@ -0,0 +1,74 @@
// 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;
import com.cloud.network.vpc.Vpc;
/**
* Implemented by network elements that support running custom actions on a
* managed network or VPC (e.g. NetworkExtensionElement).
*
* <p>This interface is looked up by {@code ExtensionsManagerImpl} to dispatch
* {@code runCustomAction} requests whose resource type is {@code Network}
* or {@code Vpc}.</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);
/**
* Returns {@code true} if this provider can handle custom actions for
* the given VPC.
*
* @param vpc the target VPC
* @return {@code true} if this provider can handle the VPC
*/
boolean canHandleVpcCustomAction(Vpc vpc);
/**
* 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);
/**
* Runs a named custom action against the external network device that
* manages the given VPC.
*
* @param vpc the CloudStack VPC on which to run the action
* @param actionName the action name
* @param parameters optional parameters supplied by the caller
* @return output from the action script, or {@code null} on failure
*/
String runCustomAction(Vpc vpc, String actionName, Map<String, Object> parameters);
}

View File

@ -0,0 +1,73 @@
// 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 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.assertNull;
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));
}
@Test
public void testCustomActionServiceLookup() {
Network.Service customAction = Network.Service.getService("CustomAction");
assertNotNull("CustomAction service should be available", customAction);
assertTrue("CustomAction should be part of the supported services list",
Network.Service.listAllServices().contains(customAction));
}
@Test
public void testTransientProviderIsNotGloballyRegistered() {
Network.Provider transientProvider = Network.Provider.createTransientProvider("TransientOnly");
assertNotNull(transientProvider);
assertNull("Transient provider should not be retrievable from the global registry",
Network.Provider.getProvider("TransientOnly"));
}
}

View File

@ -0,0 +1,92 @@
// 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
public class CustomActionResultResponseTest {
private CustomActionResultResponse response;
@Before
public void setUp() {
response = new CustomActionResultResponse();
}
@Test
public void getResultReturnsNullByDefault() {
assertNull(response.getResult());
}
@Test
public void getResultReturnsSetValue() {
Map<String, String> result = Map.of("message", "OK", "details", "All good");
response.setResult(result);
assertEquals(result, response.getResult());
assertEquals("OK", response.getResult().get("message"));
}
@Test
public void isSuccessReturnsFalseWhenSuccessIsNull() {
// success is null by default
assertFalse(response.isSuccess());
}
@Test
public void isSuccessReturnsFalseWhenSuccessIsFalse() {
response.setSuccess(false);
assertFalse(response.isSuccess());
}
@Test
public void isSuccessReturnsTrueWhenSuccessIsTrue() {
response.setSuccess(true);
assertTrue(response.isSuccess());
}
@Test
public void getSuccessReturnsNullByDefault() {
assertNull(response.getSuccess());
}
@Test
public void getSuccessReturnsTrueAfterSetSuccessTrue() {
response.setSuccess(true);
assertTrue(response.getSuccess());
}
@Test
public void getSuccessReturnsFalseAfterSetSuccessFalse() {
response.setSuccess(false);
assertFalse(response.getSuccess());
}
@Test
public void setAndGetResultWithNullResult() {
response.setResult(null);
assertNull(response.getResult());
}
}

View File

@ -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());

View File

@ -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,12 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
if (provider == null) {
provider = _networkModel.getDefaultUniqueProviderForService(service).getName();
} else {
final Provider resolvedProvider = _networkModel.resolveProvider(provider);
if (resolvedProvider == null) {
throw new InvalidParameterValueException("Invalid provider " + provider + " configured for service " + service);
}
provider = resolvedProvider.getName();
}
// check that provider is supported
@ -4480,7 +4530,10 @@ 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));
final Provider provider = _networkModel.resolveProvider(providerName);
if (provider != null) {
providers.add(provider);
}
}
return providers;
@ -4646,7 +4699,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 +4984,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(vmIds)) {
return;
}
for (NetworkElement element : networkElements) {
for (NetworkElement element : getNetworkElementsIncludingExtensions()) {
if (element instanceof LoadBalancingServiceProvider) {
LoadBalancingServiceProvider lbProvider = (LoadBalancingServiceProvider)element;
lbProvider.expungeLbVmRefs(vmIds, batchSize);

View File

@ -27,6 +27,7 @@ import static org.mockito.Mockito.when;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -35,11 +36,15 @@ 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.Extension;
import org.apache.cloudstack.extension.ExtensionHelper;
import org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.springframework.test.util.ReflectionTestUtils;
import org.mockito.ArgumentMatchers;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
@ -68,6 +73,7 @@ import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.PhysicalNetworkVO;
import com.cloud.network.dao.RouterNetworkDao;
import com.cloud.network.element.DhcpServiceProvider;
import com.cloud.network.element.NetworkElement;
import com.cloud.network.guru.GuestNetworkGuru;
import com.cloud.network.guru.NetworkGuru;
import com.cloud.network.vpc.VpcManager;
@ -105,6 +111,7 @@ public class NetworkOrchestratorTest extends TestCase {
private String guruName = "GuestNetworkGuru";
private String dhcpProvider = "VirtualRouter";
private NetworkGuru guru = mock(NetworkGuru.class);
private NetworkExtensionElement networkExtensionElement;
NetworkOfferingVO networkOffering = mock(NetworkOfferingVO.class);
@ -135,6 +142,9 @@ 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);
networkExtensionElement = mock(NetworkExtensionElement.class);
ReflectionTestUtils.setField(testOrchestrator, "networkExtensionElement", networkExtensionElement);
DhcpServiceProvider provider = mock(DhcpServiceProvider.class);
Map<Network.Capability, String> capabilities = new HashMap<Network.Capability, String>();
@ -1010,4 +1020,63 @@ public class NetworkOrchestratorTest extends TestCase {
assertEquals("testtag", nicProfile.getName());
}
}
// -----------------------------------------------------------------------
// Tests for getNetworkElementsIncludingExtensions
// -----------------------------------------------------------------------
@Test
public void getNetworkElementsIncludingExtensionsReturnsBaseListWhenNoExtensions() {
when(testOrchestrator.extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator))
.thenReturn(Collections.emptyList());
DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class);
List<NetworkElement> elements = new ArrayList<>(List.of(dhcpProvider));
testOrchestrator.networkElements = elements;
@SuppressWarnings("unchecked")
List<NetworkElement> result =
(List<NetworkElement>) ReflectionTestUtils
.invokeMethod(testOrchestrator, "getNetworkElementsIncludingExtensions");
assertNotNull(result);
assertEquals(elements.size(), result.size());
}
@Test
public void getNetworkElementsIncludingExtensionsAddsExtensionElements() {
Extension ext = mock(Extension.class);
when(ext.getName()).thenReturn("my-net-ext");
when(testOrchestrator.extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator))
.thenReturn(List.of(ext));
NetworkExtensionElement extElement = mock(NetworkExtensionElement.class);
when(networkExtensionElement.withProviderName("my-net-ext")).thenReturn(extElement);
DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class);
testOrchestrator.networkElements = new ArrayList<>(List.of(dhcpProvider));
@SuppressWarnings("unchecked")
List<NetworkElement> result =
(List<NetworkElement>) ReflectionTestUtils
.invokeMethod(testOrchestrator, "getNetworkElementsIncludingExtensions");
assertNotNull(result);
assertEquals(2, result.size());
assertTrue(result.contains(extElement));
}
@Test
public void getNetworkElementsIncludingExtensionsReturnsBaseListWhenExtensionHelperReturnsNull() {
when(testOrchestrator.extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator))
.thenReturn(null);
DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class);
testOrchestrator.networkElements = new ArrayList<>(List.of(dhcpProvider));
@SuppressWarnings("unchecked")
List<NetworkElement> result =
(List<NetworkElement>) ReflectionTestUtils
.invokeMethod(testOrchestrator, "getNetworkElementsIncludingExtensions");
assertNotNull(result);
assertEquals(1, result.size());
}
}

View File

@ -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();

View File

@ -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[");

View File

@ -97,6 +97,9 @@ public class PhysicalNetworkServiceProviderVO implements PhysicalNetworkServiceP
@Column(name = "networkacl_service_provided")
boolean networkAclServiceProvided;
@Column(name = "custom_action_service_provided")
boolean customActionServiceProvided;
@Column(name = GenericDao.REMOVED_COLUMN)
Date removed;
@ -278,6 +281,7 @@ public class PhysicalNetworkServiceProviderVO implements PhysicalNetworkServiceP
this.setUserdataServiceProvided(services.contains(Service.UserData));
this.setSecuritygroupServiceProvided(services.contains(Service.SecurityGroup));
this.setNetworkAclServiceProvided(services.contains(Service.NetworkACL));
this.setCustomActionServiceProvided(services.contains(Service.CustomAction));
}
@Override
@ -316,6 +320,9 @@ public class PhysicalNetworkServiceProviderVO implements PhysicalNetworkServiceP
if (this.isSecuritygroupServiceProvided()) {
services.add(Service.SecurityGroup);
}
if (this.isCustomActionServiceProvided()) {
services.add(Service.CustomAction);
}
return services;
}
@ -327,4 +334,12 @@ public class PhysicalNetworkServiceProviderVO implements PhysicalNetworkServiceP
public void setNetworkAclServiceProvided(boolean networkAclServiceProvided) {
this.networkAclServiceProvided = networkAclServiceProvided;
}
public boolean isCustomActionServiceProvided() {
return customActionServiceProvided;
}
public void setCustomActionServiceProvided(boolean customActionServiceProvided) {
this.customActionServiceProvided = customActionServiceProvided;
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -131,3 +131,10 @@ 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)');
-- Add CustomAction service support to physical_network_service_providers
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.physical_network_service_providers', 'custom_action_service_provided', 'tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT "Is Custom Action service provided"');

View File

@ -70,6 +70,11 @@ 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)",
since = "4.23.0")
private String type;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -82,6 +87,10 @@ public class ListExtensionsCmd extends BaseListCmd {
return extensionId;
}
public String getType() {
return type;
}
public EnumSet<ApiConstants.ExtensionDetails> getDetails() throws InvalidParameterValueException {
if (CollectionUtils.isEmpty(details)) {
return EnumSet.of(ApiConstants.ExtensionDetails.all);

View File

@ -0,0 +1,116 @@
// 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();
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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")

View File

@ -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")

View File

@ -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>

View File

@ -18,6 +18,7 @@
package org.apache.cloudstack.framework.extensions.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
@ -89,4 +90,21 @@ public class ListExtensionsCmdTest {
setPrivateField("details", detailsList);
cmd.getDetails();
}
// -----------------------------------------------------------------------
// Tests for type getter
// -----------------------------------------------------------------------
@Test
public void testGetTypeReturnsValueWhenSet() {
setPrivateField("type", "NetworkOrchestrator");
assertEquals("NetworkOrchestrator", cmd.getType());
}
@Test
public void testGetTypeReturnsNullWhenUnset() {
setPrivateField("type", null);
assertNull(cmd.getType());
}
}

View File

@ -0,0 +1,160 @@
// 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.assertFalse;
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.ApiCommandResourceType;
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;
import com.cloud.user.Account;
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 extensionIdReturnsValueWhenSet() {
Long extensionId = 42L;
ReflectionTestUtils.setField(cmd, "extensionId", extensionId);
assertEquals(extensionId, cmd.getExtensionId());
}
@Test
public void cleanupDetailsReturnsNullWhenUnset() {
ReflectionTestUtils.setField(cmd, "cleanupDetails", null);
assertNull(cmd.isCleanupDetails());
}
@Test
public void cleanupDetailsReturnsTrueWhenSet() {
ReflectionTestUtils.setField(cmd, "cleanupDetails", true);
assertTrue(cmd.isCleanupDetails());
}
@Test
public void cleanupDetailsReturnsFalseWhenSetToFalse() {
ReflectionTestUtils.setField(cmd, "cleanupDetails", false);
assertFalse(cmd.isCleanupDetails());
}
@Test
public void getEntityOwnerIdReturnsSystemAccountId() {
assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId());
}
@Test
public void getApiResourceTypeReturnsExtension() {
assertEquals(ApiCommandResourceType.Extension, cmd.getApiResourceType());
}
@Test
public void getApiResourceIdReturnsExtensionId() {
Long extensionId = 99L;
ReflectionTestUtils.setField(cmd, "extensionId", extensionId);
assertEquals(extensionId, cmd.getApiResourceId());
}
@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());
}
}
}

View File

@ -83,4 +83,55 @@ public class ExtensionResourceMapDaoImplTest {
when(dao.listResourceIdsByExtensionIdAndType(1L, ExtensionResourceMap.ResourceType.Cluster)).thenReturn(expectedIds);
assertEquals(expectedIds, dao.listResourceIdsByExtensionIdAndType(1L, ExtensionResourceMap.ResourceType.Cluster));
}
// -----------------------------------------------------------------------
// Tests for new methods: listByResourceIdAndType, listResourceIdsByType
// -----------------------------------------------------------------------
@Test
public void listByResourceIdAndTypeReturnsEmptyListWhenNoMatch() {
when(dao.listByResourceIdAndType(999L, ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(List.of());
assertTrue(dao.listByResourceIdAndType(999L, ExtensionResourceMap.ResourceType.PhysicalNetwork).isEmpty());
}
@Test
public void listByResourceIdAndTypeReturnsMatchingEntries() {
ExtensionResourceMapVO map1 = new ExtensionResourceMapVO();
map1.setResourceId(42L);
map1.setResourceType(ExtensionResourceMap.ResourceType.PhysicalNetwork);
ExtensionResourceMapVO map2 = new ExtensionResourceMapVO();
map2.setResourceId(42L);
map2.setResourceType(ExtensionResourceMap.ResourceType.PhysicalNetwork);
List<ExtensionResourceMapVO> expected = List.of(map1, map2);
when(dao.listByResourceIdAndType(42L, ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(expected);
List<ExtensionResourceMapVO> result = dao.listByResourceIdAndType(42L, ExtensionResourceMap.ResourceType.PhysicalNetwork);
assertEquals(2, result.size());
assertEquals(expected, result);
}
@Test
public void listByResourceIdAndTypeDifferentiatesResourceTypes() {
ExtensionResourceMapVO clusterMap = new ExtensionResourceMapVO();
clusterMap.setResourceType(ExtensionResourceMap.ResourceType.Cluster);
when(dao.listByResourceIdAndType(10L, ExtensionResourceMap.ResourceType.Cluster)).thenReturn(List.of(clusterMap));
when(dao.listByResourceIdAndType(10L, ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(List.of());
assertEquals(1, dao.listByResourceIdAndType(10L, ExtensionResourceMap.ResourceType.Cluster).size());
assertTrue(dao.listByResourceIdAndType(10L, ExtensionResourceMap.ResourceType.PhysicalNetwork).isEmpty());
}
@Test
public void listResourceIdsByTypeReturnsEmptyListWhenNoMatch() {
when(dao.listResourceIdsByType(ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(List.of());
assertTrue(dao.listResourceIdsByType(ExtensionResourceMap.ResourceType.PhysicalNetwork).isEmpty());
}
@Test
public void listResourceIdsByTypeReturnsMatchingIds() {
List<Long> expectedIds = List.of(5L, 10L, 15L);
when(dao.listResourceIdsByType(ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(expectedIds);
List<Long> result = dao.listResourceIdsByType(ExtensionResourceMap.ResourceType.PhysicalNetwork);
assertEquals(3, result.size());
assertEquals(expectedIds, result);
}
}

View File

@ -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);

View File

@ -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,9 +3309,11 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
}
response.setServices(services);
Provider serviceProvider = Provider.getProvider(result.getProviderName());
boolean canEnableIndividualServices = ApiDBUtils.canElementEnableIndividualServices(serviceProvider);
response.setCanEnableIndividualServices(canEnableIndividualServices);
Provider serviceProvider = networkModel.resolveProvider(result.getProviderName());
if (serviceProvider != null) {
boolean canEnableIndividualServices = ApiDBUtils.canElementEnableIndividualServices(serviceProvider);
response.setCanEnableIndividualServices(canEnableIndividualServices);
}
response.setObjectName("networkserviceprovider");
return response;

View File

@ -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) {

View File

@ -99,6 +99,9 @@ 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.Extension;
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 +237,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 +270,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 +315,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 +485,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 +534,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 +776,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 +816,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 +855,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 +1086,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 +1267,11 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
if (providers == null) {
providers = new HashSet<Provider>();
}
providers.add(Provider.getProvider(instance.getProvider()));
final Provider provider = resolveProvider(instance.getProvider());
if (provider != null) {
providers.add(provider);
}
serviceProviderMap.put(Service.getService(service), providers);
}
@ -1201,9 +1311,87 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
}
}
// Also include extension-backed NetworkExtension providers.
// resolveProvider() creates a transient Provider (not added to the static list)
// for extension names that are not in the built-in registry.
try {
List<PhysicalNetworkServiceProviderVO> nsps = _pNSPDao.listAll();
if (CollectionUtils.isNotEmpty(nsps)) {
Set<String> extensionProviderNames = new HashSet<>();
List<Extension> extensions = extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator);
if (extensions != null) {
for (Extension extension : extensions) {
if (extension == null || StringUtils.isBlank(extension.getName())) {
continue;
}
extensionProviderNames.add(extension.getName().toLowerCase());
}
}
if (!extensionProviderNames.isEmpty()) {
// Avoid duplicate provider names across multiple physical networks.
Set<String> addedExtProviders = new HashSet<>();
for (PhysicalNetworkServiceProviderVO nsp : nsps) {
String provName = nsp.getProviderName();
if (StringUtils.isBlank(provName)) {
continue;
}
if (addedExtProviders.contains(provName) || !extensionProviderNames.contains(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#listAll})
* 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 +1728,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 +1774,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 +1849,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 +1894,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 +2178,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 +2480,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));
}
}

View File

@ -3087,8 +3087,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;
}
}
@ -4449,6 +4449,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
addOrRemoveVnets(listOfRanges, network);
}
_physicalNetworkDao.update(id, network);
return network;
}
@ -5121,7 +5122,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 +5159,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);
}

View File

@ -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");
}

View File

@ -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,38 @@ 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);
if (element instanceof FirewallServiceProvider) {
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);
if (element instanceof PortForwardingServiceProvider) {
handled = ((PortForwardingServiceProvider) element).applyPFRules(network, (List<PortForwardingRule>) rules);
}
}
}
break;
/* case NetworkACL:
for (NetworkACLServiceProvider element: _networkAclElements) {
@ -726,7 +764,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);
}

View File

@ -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,16 @@ 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);
if (element instanceof LoadBalancingServiceProvider) {
handled = ((LoadBalancingServiceProvider) element).applyLBRules(network, rules);
}
}
}
return handled;
}

View File

@ -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,26 @@ 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);
if (element instanceof NetworkACLServiceProvider) {
logger.debug("Applying NetworkACL for network: {} with Network ACL service provider: {}", network, 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);
}

View File

@ -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,12 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
if (provider == null) {
// Default to VPCVirtualRouter
provider = Provider.VPCVirtualRouter.getName();
} else {
final Provider resolvedProvider = _ntwkModel.resolveProvider(provider);
if (resolvedProvider == null) {
throw new InvalidParameterValueException("Invalid provider " + provider + " configured for service " + service);
}
provider = resolvedProvider.getName();
}
if (!_ntwkModel.isProviderEnabledInZone(zoneId, provider)) {
@ -2035,9 +2051,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 +2530,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 +2638,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 +3972,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));
}
}

View File

@ -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[])
*/

View File

@ -18,6 +18,9 @@ package com.cloud.network;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@ -54,6 +57,9 @@ import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkServiceMapDao;
import com.cloud.network.dao.NetworkServiceMapVO;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
import com.cloud.network.dao.PhysicalNetworkServiceProviderVO;
import com.cloud.network.element.NetworkElement;
import com.cloud.network.element.VpcVirtualRouterElement;
import com.cloud.network.vpc.VpcVO;
@ -66,6 +72,9 @@ 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.Extension;
import org.apache.cloudstack.extension.ExtensionHelper;
import org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement;
@RunWith(MockitoJUnitRunner.class)
public class NetworkModelImplTest {
@ -80,6 +89,17 @@ public class NetworkModelImplTest {
private NetworkDao _networksDao;
@Inject
private NetworkOfferingServiceMapDao networkOfferingServiceMapDao;
@Mock
private ExtensionHelper extensionHelper;
@Mock
private NetworkExtensionElement networkExtensionElement;
@Mock
private PhysicalNetworkDao physicalNetworkDao;
@Mock
private PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao;
@Spy
@InjectMocks
@ -96,6 +116,11 @@ public class NetworkModelImplTest {
networkModel._networkOfferingDao = networkOfferingDao;
networkModel._ntwkSrvcDao = networkServiceMapDao;
networkModel._ntwkOfferingSrvcDao = networkOfferingServiceMapDao;
ReflectionTestUtils.setField(networkModel, "extensionHelper", extensionHelper);
ReflectionTestUtils.setField(networkModel, "networkExtensionElement", networkExtensionElement);
ReflectionTestUtils.setField(networkModel, "_physicalNetworkDao", physicalNetworkDao);
ReflectionTestUtils.setField(networkModel, "_pNSPDao", physicalNetworkServiceProviderDao);
Mockito.lenient().when(extensionHelper.isNetworkExtensionProvider(Mockito.anyString())).thenReturn(false);
}
private void prepareMocks(boolean isIp6, Network network, DataCenter zone, VpcVO vpc,
@ -242,8 +267,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));
@ -273,4 +298,120 @@ public class NetworkModelImplTest {
assertNotNull(result);
}
// -----------------------------------------------------------------------
// Tests for getElementImplementingProvider with extension provider
// -----------------------------------------------------------------------
@Test
public void getElementImplementingProviderReturnsExtensionElementForExtensionProvider() {
String providerName = "my-ext-provider";
// Provider is not in the static map, so element would be null
ReflectionTestUtils.setField(networkModel, "networkElements", new ArrayList<>());
when(extensionHelper.isNetworkExtensionProvider(providerName)).thenReturn(true);
NetworkExtensionElement mockElement = mock(NetworkExtensionElement.class);
when(networkExtensionElement.withProviderName(providerName)).thenReturn(mockElement);
NetworkElement result = networkModel.getElementImplementingProvider(providerName);
// When the element is a NetworkExtensionElement (which is a NetworkElement), result should not be null
assertNotNull(result);
}
@Test
public void getElementImplementingProviderReturnsNullForUnknownNonExtensionProvider() {
String providerName = "unknown-provider";
ReflectionTestUtils.setField(networkModel, "networkElements", new ArrayList<>());
when(extensionHelper.isNetworkExtensionProvider(providerName)).thenReturn(false);
NetworkElement result = networkModel.getElementImplementingProvider(providerName);
assertNull(result);
}
// -----------------------------------------------------------------------
// Tests for resolveProvider
// -----------------------------------------------------------------------
@Test
public void resolveProviderReturnsKnownProvider() {
Network.Provider result = networkModel.resolveProvider(Network.Provider.VirtualRouter.getName());
assertNotNull(result);
assertEquals(Network.Provider.VirtualRouter, result);
}
@Test
public void resolveProviderReturnsTransientProviderForExtensionProvider() {
String extensionName = "my-ext-network-provider";
when(extensionHelper.isNetworkExtensionProvider(extensionName)).thenReturn(true);
Network.Provider result = networkModel.resolveProvider(extensionName);
assertNotNull(result);
assertEquals(extensionName, result.getName());
}
@Test
public void resolveProviderReturnsNullForUnknownNonExtensionProvider() {
String providerName = "totally-unknown";
when(extensionHelper.isNetworkExtensionProvider(providerName)).thenReturn(false);
Network.Provider result = networkModel.resolveProvider(providerName);
assertNull(result);
}
// -----------------------------------------------------------------------
// Tests for canElementEnableIndividualServicesByName
// -----------------------------------------------------------------------
@Test
public void canElementEnableIndividualServicesByNameReturnsFalseForNullProvider() {
assertFalse(networkModel.canElementEnableIndividualServicesByName(null));
}
@Test
public void canElementEnableIndividualServicesByNameReturnsFalseForUnknownProvider() {
when(extensionHelper.isNetworkExtensionProvider("unknown")).thenReturn(false);
assertFalse(networkModel.canElementEnableIndividualServicesByName("unknown"));
}
// -----------------------------------------------------------------------
// Tests for getExternalProviderCapabilities
// -----------------------------------------------------------------------
@Test
public void getExternalProviderCapabilitiesCallsExtensionHelper() {
Map<Network.Service, Map<Network.Capability, String>> caps = new HashMap<>();
when(extensionHelper.getNetworkCapabilitiesForProvider(10L, "my-ext")).thenReturn(caps);
Map<Network.Service, Map<Network.Capability, String>> result =
networkModel.getExternalProviderCapabilities(10L, "my-ext");
assertEquals(caps, result);
}
// -----------------------------------------------------------------------
// Tests for isServiceProvidedByNsp (via listSupportedNetworkServiceProviders)
// -----------------------------------------------------------------------
@Test
public void listSupportedNetworkServiceProvidersIncludesExtensionBackedProviders() {
PhysicalNetworkServiceProviderVO nsp = mock(PhysicalNetworkServiceProviderVO.class);
when(nsp.getProviderName()).thenReturn("my-ext");
when(physicalNetworkServiceProviderDao.listAll()).thenReturn(List.of(nsp));
Extension extension = mock(Extension.class);
when(extension.getName()).thenReturn("my-ext");
when(extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator)).thenReturn(List.of(extension));
when(extensionHelper.isNetworkExtensionProvider("my-ext")).thenReturn(true);
// networkElements is empty so no standard providers found
ReflectionTestUtils.setField(networkModel, "networkElements", new ArrayList<>());
// We call with null service to test the inclusion path (parameter is service name String)
List<? extends Network.Provider> result = networkModel.listSupportedNetworkServiceProviders(null);
boolean found = result.stream().anyMatch(p -> "my-ext".equalsIgnoreCase(p.getName()));
assertTrue("Extension-backed provider should be included", found);
Mockito.verify(physicalNetworkServiceProviderDao, Mockito.times(1)).listAll();
Mockito.verify(physicalNetworkServiceProviderDao, Mockito.never()).listBy(Mockito.anyLong());
}
}

View File

@ -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());

View File

@ -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();
}

View File

@ -0,0 +1,178 @@
// 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 com.cloud.network.firewall;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.Network;
import com.cloud.network.NetworkModel;
import com.cloud.network.dao.NetworkServiceMapDao;
import com.cloud.network.element.FirewallServiceProvider;
import com.cloud.network.element.NetworkElement;
import com.cloud.network.element.PortForwardingServiceProvider;
import com.cloud.network.rules.FirewallRule;
import com.cloud.network.rules.PortForwardingRule;
@RunWith(MockitoJUnitRunner.Silent.class)
public class FirewallManagerImplTest {
interface MockFwElement extends NetworkElement, FirewallServiceProvider {}
interface MockPfElement extends NetworkElement, PortForwardingServiceProvider {}
@Spy
@InjectMocks
private FirewallManagerImpl firewallManager;
@Mock
private NetworkModel _networkModel;
@Mock
private NetworkServiceMapDao networkServiceMapDao;
@Before
public void setUp() {
ReflectionTestUtils.setField(firewallManager, "_firewallElements", Collections.emptyList());
ReflectionTestUtils.setField(firewallManager, "_pfElements", Collections.emptyList());
ReflectionTestUtils.setField(firewallManager, "_staticNatElements", Collections.emptyList());
ReflectionTestUtils.setField(firewallManager, "_networkAclElements", Collections.emptyList());
}
// -----------------------------------------------------------------------
// Tests for applyRules with extension-backed Firewall provider
// -----------------------------------------------------------------------
@Test
public void applyRulesFirewallHandledByExtensionProvider() throws ResourceUnavailableException {
Network network = mock(Network.class);
when(network.getId()).thenReturn(1L);
FirewallRule rule = mock(FirewallRule.class);
List<? extends FirewallRule> rules = List.of(rule);
// No standard firewall elements handle it
String extProviderName = "my-ext-fw-provider";
when(networkServiceMapDao.getProviderForServiceInNetwork(1L, Network.Service.Firewall))
.thenReturn(extProviderName);
// The element implementing the provider is both NetworkElement and FirewallServiceProvider
MockFwElement element = mock(MockFwElement.class);
when(element.applyFWRules(eq(network), any())).thenReturn(true);
when(_networkModel.getElementImplementingProvider(extProviderName)).thenReturn(element);
boolean result = firewallManager.applyRules(network, FirewallRule.Purpose.Firewall, rules);
assertTrue(result);
verify(element).applyFWRules(eq(network), any());
}
@Test
public void applyRulesFirewallReturnsFalseWhenNoExtensionProviderFound() throws ResourceUnavailableException {
Network network = mock(Network.class);
when(network.getId()).thenReturn(2L);
FirewallRule rule = mock(FirewallRule.class);
List<? extends FirewallRule> rules = List.of(rule);
// No standard provider and no extension provider found
when(networkServiceMapDao.getProviderForServiceInNetwork(2L, Network.Service.Firewall))
.thenReturn(null);
boolean result = firewallManager.applyRules(network, FirewallRule.Purpose.Firewall, rules);
assertFalse(result);
verify(_networkModel, never()).getElementImplementingProvider(any());
}
// -----------------------------------------------------------------------
// Tests for applyRules with extension-backed PortForwarding provider
// -----------------------------------------------------------------------
@Test
public void applyRulesPortForwardingHandledByExtensionProvider() throws ResourceUnavailableException {
Network network = mock(Network.class);
when(network.getId()).thenReturn(3L);
PortForwardingRule rule = mock(PortForwardingRule.class);
@SuppressWarnings("unchecked")
List<PortForwardingRule> rules = List.of(rule);
String extProviderName = "my-ext-pf-provider";
when(networkServiceMapDao.getProviderForServiceInNetwork(3L, Network.Service.PortForwarding))
.thenReturn(extProviderName);
MockPfElement element = mock(MockPfElement.class);
when(element.applyPFRules(eq(network), any())).thenReturn(true);
when(_networkModel.getElementImplementingProvider(extProviderName)).thenReturn(element);
boolean result = firewallManager.applyRules(network, FirewallRule.Purpose.PortForwarding, rules);
assertTrue(result);
verify(element).applyPFRules(eq(network), any());
}
@Test
public void applyRulesPortForwardingReturnsFalseWhenNoExtensionProviderFound() throws ResourceUnavailableException {
Network network = mock(Network.class);
when(network.getId()).thenReturn(4L);
PortForwardingRule rule = mock(PortForwardingRule.class);
List<? extends FirewallRule> rules = List.of(rule);
when(networkServiceMapDao.getProviderForServiceInNetwork(4L, Network.Service.PortForwarding))
.thenReturn(null);
boolean result = firewallManager.applyRules(network, FirewallRule.Purpose.PortForwarding, rules);
assertFalse(result);
}
// -----------------------------------------------------------------------
// Tests for StaticNat (handled by Firewall elements)
// -----------------------------------------------------------------------
@Test
public void applyRulesStaticNatReturnsFalseWhenNoProviderFound() throws ResourceUnavailableException {
Network network = mock(Network.class);
when(network.getId()).thenReturn(5L);
FirewallRule rule = mock(FirewallRule.class);
List<? extends FirewallRule> rules = List.of(rule);
when(networkServiceMapDao.getProviderForServiceInNetwork(5L, Network.Service.Firewall))
.thenReturn(null);
boolean result = firewallManager.applyRules(network, FirewallRule.Purpose.StaticNat, rules);
assertFalse(result);
}
}

View File

@ -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);

View File

@ -43,6 +43,7 @@ import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.element.NetworkElement;
import com.cloud.network.element.VpcProvider;
import com.cloud.network.router.CommandSetupHelper;
import com.cloud.network.router.NetworkHelper;
import com.cloud.network.router.VirtualRouter;
@ -72,6 +73,8 @@ import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
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.network.Ipv4GuestSubnetNetworkMap;
import org.apache.cloudstack.network.RoutedIpv4Manager;
@ -90,6 +93,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -99,6 +103,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 +252,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() {
@ -601,4 +613,49 @@ public class VpcManagerImplTest {
Assert.assertTrue(manager.isNetworkOnVpcEnabledConserveMode(network));
}
// -----------------------------------------------------------------------
// Tests for getVpcElements with extension-backed NetworkOrchestrator
// -----------------------------------------------------------------------
@Test
public void getVpcElementsIncludesExtensionBackedVpcProvider() {
manager.setVpcElements(null);
Mockito.when(networkModel.getElementImplementingProvider(Provider.VPCVirtualRouter.getName())).thenReturn(null);
Mockito.when(networkModel.getElementImplementingProvider(Provider.JuniperContrailVpcRouter.getName())).thenReturn(null);
Extension ext = mock(Extension.class);
Mockito.when(ext.getName()).thenReturn("my-vpc-ext");
ExtensionHelper extHelper = mock(ExtensionHelper.class);
Mockito.when(extHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator))
.thenReturn(List.of(ext));
manager.extensionHelper = extHelper;
// The element for the extension also implements VpcProvider
VpcProvider vpcProviderElement = mock(VpcProvider.class);
Mockito.when(networkModel.getElementImplementingProvider("my-vpc-ext")).thenReturn((NetworkElement) vpcProviderElement);
List<VpcProvider> result = manager.getVpcElements();
Assert.assertNotNull(result);
Assert.assertTrue(result.contains(vpcProviderElement));
}
@Test
public void getVpcElementsReturnsEmptyListWhenNoStaticNorExtensionProviders() {
manager.setVpcElements(null);
Mockito.when(networkModel.getElementImplementingProvider(Provider.VPCVirtualRouter.getName())).thenReturn(null);
Mockito.when(networkModel.getElementImplementingProvider(Provider.JuniperContrailVpcRouter.getName())).thenReturn(null);
ExtensionHelper extHelper = mock(ExtensionHelper.class);
Mockito.when(extHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator))
.thenReturn(Collections.emptyList());
manager.extensionHelper = extHelper;
List<VpcProvider> result = manager.getVpcElements();
Assert.assertNotNull(result);
Assert.assertTrue(result.isEmpty());
}
}

View File

@ -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[])
*/

View File

@ -2120,6 +2120,7 @@ CREATE TABLE `cloud`.`physical_network_service_providers` (
`user_data_service_provided` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is UserData service provided',
`security_group_service_provided` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is SG service provided',
`networkacl_service_provided` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is Network ACL service provided',
`custom_action_service_provided` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Is Custom Action service provided',
`removed` datetime COMMENT 'date removed if not null',
PRIMARY KEY (`id`),
CONSTRAINT `fk_pnetwork_service_providers__physical_network_id` FOREIGN KEY (`physical_network_id`) REFERENCES `physical_network`(`id`) ON DELETE CASCADE,

File diff suppressed because it is too large Load Diff

View File

@ -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",
@ -1095,6 +1096,7 @@
"label.external.details.tooltip": "Details that will be passed to the external provisioner while deploying an instance",
"label.externalprovisioner": "External provisioner",
"label.external.link": "External link",
"label.external.network.provider": "External Network Provider",
"label.externalid": "External Id",
"label.externalloadbalanceripaddress": "External load balancer IP address.",
"label.extra": "Extra arguments",
@ -4176,6 +4178,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",

View File

@ -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')))

View File

@ -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',

View File

@ -118,6 +118,14 @@ export default {
name: 'network.permissions',
component: shallowRef(defineAsyncComponent(() => import('@/views/network/NetworkPermissions.vue'))),
show: (record, route, user) => { return 'listNetworkPermissions' in store.getters.apis && record.acltype === 'Account' && !('vpcid' in record) && (['Admin', 'DomainAdmin'].includes(user.roletype) || record.account === user.account) && !record.projectid }
}, {
name: 'custom.actions',
component: shallowRef(defineAsyncComponent(() => import('@/views/extension/RunCustomAction.vue'))),
show: (record) => {
return 'runCustomAction' in store.getters.apis &&
'listCustomActions' in store.getters.apis &&
record.service && record.service.some(s => s.name === 'CustomAction')
}
},
{
name: 'events',
@ -207,6 +215,19 @@ export default {
}
}
},
{
api: 'runCustomAction',
icon: 'thunderbolt-outlined',
label: 'label.run.custom.action',
dataView: true,
show: (record) => {
return 'runCustomAction' in store.getters.apis &&
'listCustomActions' in store.getters.apis &&
record.service && record.service.some(s => s.name === 'CustomAction')
},
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/extension/RunCustomAction.vue')))
},
{
api: 'deleteNetwork',
icon: 'delete-outlined',

View File

@ -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', 'Vpc'],
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', 'Vpc']
this.form.resourcetype = 'Network'
} else if (type === 'Orchestrator') {
this.resourceTypeOptions = ['VirtualMachine']
this.form.resourcetype = 'VirtualMachine'
} else {
this.resourceTypeOptions = ['VirtualMachine', 'Network', 'Vpc']
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']

View File

@ -162,7 +162,7 @@ export default {
},
fetchExtensionTypes () {
this.extensionTypes = []
const extensionTypesList = ['Orchestrator']
const extensionTypesList = ['Orchestrator', 'NetworkOrchestrator']
extensionTypesList.forEach((item) => {
this.extensionTypes.push({
id: item,

View File

@ -34,6 +34,14 @@
{{ text && $toLocaleDate(text) }}
</template>
<template v-if="column.key === 'actions'">
<span style="margin-right: 5px">
<tooltip-button
v-if="'updateRegisteredExtension' in $store.getters.apis"
: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 +67,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 +89,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 +128,9 @@ export default {
title: this.$t('label.actions')
}
],
unregisterLoading: false
unregisterLoading: false,
updateModalVisible: false,
selectedResource: null
}
},
computed: {
@ -112,13 +139,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 +168,7 @@ export default {
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.deleteLoading = false
this.unregisterLoading = false
})
}
}

View File

@ -120,7 +120,7 @@ export default {
},
fetchExtensionResourceTypes () {
this.resourceTypes = []
const resourceTypesList = ['Cluster']
const resourceTypesList = ['Cluster', 'PhysicalNetwork']
resourceTypesList.forEach((item) => {
this.resourceTypes.push({
id: item,

View File

@ -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>

View File

@ -706,6 +706,7 @@ export default {
}
this.loading = true
getAPI('listExtensions', {
type: 'Orchestrator'
}).then(response => {
this.extensionsList = response.listextensionsresponse.extension || []
}).catch(error => {

View File

@ -302,6 +302,7 @@ export default {
fetchExtensionsList () {
this.loading = true
getAPI('listExtensions', {
type: 'Orchestrator'
}).then(response => {
this.extensionsList = response.listextensionsresponse.extension || []
}).catch(error => {

View File

@ -22,6 +22,7 @@
: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 +42,29 @@
: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>
<div v-if="showFormAction">
<keep-alive v-if="currentAction.component">
<a-modal
@ -170,7 +192,8 @@ export default {
actionLoading: false,
showFormAction: false,
currentAction: {},
tabKey: 'BaremetalDhcpProvider'
tabKey: 'BaremetalDhcpProvider',
registeredExtensions: []
}
},
computed: {
@ -848,7 +871,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' }
@ -1148,6 +1171,51 @@ export default {
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

View File

@ -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
}

View File

@ -405,6 +405,9 @@
<a-tab-pane :tab="$t('label.vnf.appliances')" key="vnf" v-if="'deployVnfAppliance' in $store.getters.apis">
<VnfAppliancesTab :resource="resource" :loading="loading" />
</a-tab-pane>
<a-tab-pane :tab="$t('label.custom.actions')" key="customactions" v-if="'runCustomAction' in $store.getters.apis && 'listCustomActions' in $store.getters.apis && resource.service && resource.service.some(s => s.name === 'CustomAction')">
<RunCustomAction :resource="resource" />
</a-tab-pane>
<a-tab-pane :tab="$t('label.events')" key="events" v-if="'listEvents' in $store.getters.apis">
<events-tab :resource="resource" resourceType="Vpc" :loading="loading" />
</a-tab-pane>
@ -433,6 +436,7 @@ import AnnotationsTab from '@/components/view/AnnotationsTab'
import ResourceIcon from '@/components/view/ResourceIcon'
import BgpPeersTab from '@/views/infra/zone/BgpPeersTab.vue'
import StaticRoutesTab from './StaticRoutesTab'
import RunCustomAction from '@/views/extension/RunCustomAction'
export default {
name: 'VpcTab',
@ -445,6 +449,7 @@ export default {
VpcTiersTab,
VnfAppliancesTab,
StaticRoutesTab,
RunCustomAction,
EventsTab,
AnnotationsTab,
ResourceIcon

View File

@ -739,6 +739,13 @@ export default {
isSupportedServiceObject (obj) {
return (obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object && 'provider' in obj)
},
isVpcCoreProvider (providerName) {
return ['VpcVirtualRouter', 'Netscaler', 'BigSwitchBcf', 'ConfigDrive'].includes(providerName)
},
isDynamicExtensionProvider (providerName) {
const knownProviders = ['VirtualRouter', 'VpcVirtualRouter', 'InternalLbVm', 'Netscaler', 'BigSwitchBcf', 'ConfigDrive', 'Nsx', 'Netris']
return !knownProviders.includes(providerName)
},
fetchDomainData () {
const params = {}
params.listAll = true
@ -917,11 +924,13 @@ export default {
var providers = svc.provider
providers.forEach(function (provider, providerIndex) {
if (self.forVpc) { // *** vpc ***
var enabledProviders = ['VpcVirtualRouter', 'Netscaler', 'BigSwitchBcf', 'ConfigDrive']
if (self.lbType === 'internalLb') {
enabledProviders.push('InternalLbVm')
// Keep the known VPC-safe providers allowlisted and only additionally
// enable dynamically discovered extension providers.
if (provider.name === 'InternalLbVm') {
provider.enabled = self.lbType === 'internalLb' && svc.name === 'Lb'
} else {
provider.enabled = self.isVpcCoreProvider(provider.name) || self.isDynamicExtensionProvider(provider.name)
}
provider.enabled = enabledProviders.includes(provider.name)
} else { // *** non-vpc ***
provider.enabled = !['InternalLbVm', 'VpcVirtualRouter', 'Nsx', 'Netris'].includes(provider.name)
}

View File

@ -154,7 +154,7 @@
:checkBoxLabel="item.description"
:forExternalNetProvider="form.provider === 'NSX' || form.provider === 'Netris'"
:defaultCheckBoxValue="form.provider === 'NSX' || form.provider === 'Netris'"
:selectOptions="item.provider"
:selectOptions="item.provider || []"
@handle-checkselectpair-change="handleSupportedServiceChange"/>
</a-list-item>
</template>
@ -431,6 +431,13 @@ export default {
this.zoneLoading = false
})
},
isVpcCoreProvider (providerName) {
return ['VpcVirtualRouter', 'Netscaler', 'BigSwitchBcf', 'ConfigDrive'].includes(providerName)
},
isDynamicExtensionProvider (providerName) {
const knownProviders = ['VirtualRouter', 'VpcVirtualRouter', 'InternalLbVm', 'Netscaler', 'BigSwitchBcf', 'ConfigDrive', 'Nsx', 'Netris']
return !knownProviders.includes(providerName)
},
fetchSupportedServiceData () {
var services = []
if (this.provider === 'NSX') {
@ -520,82 +527,52 @@ export default {
provider: [{ name: 'VpcVirtualRouter' }]
})
} else {
services.push({
name: 'Dhcp',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'ConfigDrive' }
]
})
services.push({
name: 'Dns',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'ConfigDrive' }
]
})
services.push({
name: 'Lb',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'InternalLbVm' }
]
})
services.push({
name: 'Gateway',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'BigSwitchBcf' }
]
})
services.push({
name: 'StaticNat',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'BigSwitchBcf' }
]
})
services.push({
name: 'SourceNat',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'BigSwitchBcf' }
]
})
services.push({
name: 'NetworkACL',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'BigSwitchBcf' }
]
})
services.push({
name: 'PortForwarding',
provider: [{ name: 'VpcVirtualRouter' }]
})
services.push({
name: 'UserData',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'ConfigDrive' }
]
})
services.push({
name: 'Vpn',
provider: [
{ name: 'VpcVirtualRouter' },
{ name: 'BigSwitchBcf' }
]
})
services.push({
name: 'Connectivity',
provider: [
{ name: 'BigSwitchBcf' },
{ name: 'NiciraNvp' },
{ name: 'Ovs' },
{ name: 'JuniperContrailVpcRouter' }
]
this.supportedServices = []
this.supportedServiceLoading = true
getAPI('listSupportedNetworkServices').then(json => {
const vpcServices = ['Dhcp', 'Dns', 'Lb', 'Gateway', 'StaticNat', 'SourceNat', 'NetworkACL', 'PortForwarding', 'UserData', 'Vpn', 'Connectivity', 'CustomAction']
services = (json?.listsupportednetworkservicesresponse?.networkservice || [])
.filter(service => vpcServices.includes(service.name))
.map(service => {
const providerMap = {}
const providers = [...(service.provider || []), ...(service.name === 'Lb' ? [{ name: 'InternalLbVm' }] : [])]
.map(provider => {
const providerName = provider.name === 'VirtualRouter' ? 'VpcVirtualRouter' : provider.name
const enabled = providerName === 'InternalLbVm'
? service.name === 'Lb'
: this.isVpcCoreProvider(providerName) || this.isDynamicExtensionProvider(providerName)
return {
name: providerName,
description: providerName,
enabled
}
})
.filter(provider => {
if (providerMap[provider.name]) {
return false
}
providerMap[provider.name] = true
return true
})
return {
...service,
description: service.name,
provider: providers
}
})
this.supportedServices = []
if (this.networkmode === 'ROUTED') {
services = services.filter(service => !['SourceNat', 'StaticNat', 'Lb', 'PortForwarding', 'Vpn'].includes(service.name))
}
this.supportedServices = services
}).catch(error => {
this.supportedServices = []
this.$notifyError(error)
}).finally(() => {
this.supportedServiceLoading = false
})
return
}
this.supportedServices = []
if (this.networkmode === 'ROUTED') {
@ -647,7 +624,7 @@ export default {
if (service === 'SourceNat') {
this.sourceNatServiceChecked = checked
}
if (checked && provider != null & provider !== undefined) {
if (checked && provider != null && provider !== undefined) {
this.selectedServiceProviderMap[service] = provider
} else {
delete this.selectedServiceProviderMap[service]