mirror of
https://github.com/apache/cloudstack
synced 2026-08-02 05:26:35 +00:00
Network Extension: Orchestrate external Network devices (#13032)
This PR introduces and wires a new extension model for external network orchestration in CloudStack, centered on a new extension type: NetworkOrchestrator. It extends the extension lifecycle from cluster-only registration to physical-network registration, adds API support for updating registered extension metadata, and enables automatic offering creation (network and VPC) based on provider-declared supported services and capabilities. It also adds smoke coverage (KVM-only) using a Linux network namespace based implementation. Doc PR: apache/cloudstack-documentation#649 What's new 1) New extension type: NetworkOrchestrator Adds support for creating extensions of type NetworkOrchestrator. Intended to back CloudStack network/VPC operations via an external orchestrator/provider. 2) Register extension with PhysicalNetwork (in addition to Cluster) Extensions can now be registered against a PhysicalNetwork resource. This enables network service provider behavior at physical-network scope, not only cluster scope. 3) Physical network registration details support Registered extension details for PhysicalNetwork are handled similarly to cluster registration details. Supports storing/updating external access metadata (credentials/endpoints/config details). 4) New API: update registered extension Adds API support to update extension registration metadata after registration. Useful for rotating credentials, updating endpoints, and changing external connection properties without re-registering. 5) Offering automation from external provider capabilities Network/VPC offerings can be created with the external network provider using: provider supportedservices per-service service capabilities This allows CloudStack offerings to align with what the external provider actually supports. 6) Network support via generated offerings Using offerings backed by the external provider, networks can be created and operated with supported services/capabilities. Supported operations include (based on provider capabilities): Source NAT Static NAT Port Forwarding Firewall Load Balancing DHCP DNS UserData 7) VPC support via generated offerings Using VPC offerings backed by the external provider, VPCs and tiers can be created and operated with supported services/capabilities. Supported operations include (based on provider capabilities): VPC tier creation/implementation Source NAT in VPC context Static NAT / Port Forwarding / LB on VPC tiers Network ACL association and ACL rule apply paths Related lifecycle/restart/reapply operations 8) Linux network namespace based external implementation Adds/uses a network extension implementation based on Linux network namespaces. Reference implementation: https://github.com/apache/cloudstack-extensions/tree/network-namespace/Network-Namespace 9) Smoke test coverage (KVM-only) Adds smoke tests using the namespace-based extension implementation. Scope includes provider lifecycle, offering creation, network/VPC flows, and key network services. Applicable hypervisor for this smoke suite: KVM.
This commit is contained in:
parent
20e9980b60
commit
8996286a95
@ -875,6 +875,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";
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -33,4 +33,6 @@ public interface DnsServiceProvider extends NetworkElement {
|
||||
throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException;
|
||||
|
||||
boolean removeDnsSupportForSubnet(Network network) throws ResourceUnavailableException;
|
||||
|
||||
default boolean removeDnsEntry(Network network, NicProfile nic, VirtualMachineProfile vmProfile) throws ResourceUnavailableException { return true; }
|
||||
}
|
||||
|
||||
@ -146,4 +146,8 @@ public interface NetworkElement extends Adapter {
|
||||
* @return true/false
|
||||
*/
|
||||
boolean verifyServicesCombination(Set<Service> services);
|
||||
|
||||
default boolean rollingRestartSupported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,6 +463,6 @@ public class NicProfile implements InternalIdentity, Serializable {
|
||||
return String.format("NicProfile %s",
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "vmId", "deviceId",
|
||||
"broadcastUri", "reservationId", "iPv4Address"));
|
||||
"broadcastType", "broadcastUri", "reservationId", "iPv4Address"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,4 +62,12 @@ public class CustomActionResultResponse extends BaseResponse {
|
||||
public void setResult(Map<String, String> result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Map<String, String> getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return Boolean.TRUE.equals(success);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,8 @@ import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
public interface Extension extends InternalIdentity, Identity {
|
||||
enum Type {
|
||||
Orchestrator
|
||||
Orchestrator,
|
||||
NetworkOrchestrator
|
||||
}
|
||||
enum State {
|
||||
Enabled, Disabled;
|
||||
|
||||
@ -48,7 +48,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;
|
||||
|
||||
|
||||
@ -18,10 +18,125 @@
|
||||
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 getExtensionByNameAndType(String name, Extension.Type type);
|
||||
Extension getExtensionForCluster(long clusterId);
|
||||
List<String> getExtensionReservedResourceDetails(long extensionId);
|
||||
|
||||
/**
|
||||
* Network Guru of Network Extension
|
||||
*/
|
||||
String NETWORK_EXTENSION_GURU_NAME = "NetworkExtensionGuestNetworkGuru";
|
||||
|
||||
/**
|
||||
* Extension resource-map detail key holding the isolation method for the physical
|
||||
* network (e.g. {@code "NetworkExtension"}).
|
||||
* If not specified, the isolation method of physical network will be used.
|
||||
*/
|
||||
String NETWORK_ISOLATION_METHOD_DETAIL_KEY = "network.isolation.method";
|
||||
|
||||
/**
|
||||
* Value of {@link #NETWORK_ISOLATION_METHOD_DETAIL_KEY} that indicates
|
||||
* the extension owns the network isolation.
|
||||
*/
|
||||
String NETWORK_EXTENSION_ISOLATION_METHOD = "NetworkExtension";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
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 {@code true} when the extension registered for {@code providerName} on
|
||||
* the given physical network has its {@link #NETWORK_ISOLATION_METHOD_DETAIL_KEY}
|
||||
* resource-map detail set to {@link #NETWORK_EXTENSION_ISOLATION_METHOD}.
|
||||
*
|
||||
* @param providerName provider / extension name
|
||||
* @return true if the extension uses NetworkExtension isolation
|
||||
*/
|
||||
boolean usesNetworkExtensionIsolation(String providerName);
|
||||
|
||||
/**
|
||||
* Returns the effective {@link Service} → ({@link Capability} → value) capabilities
|
||||
* for the given external network provider, looking it up by name on the given
|
||||
* physical network.
|
||||
*
|
||||
* <p>If {@code physicalNetworkId} is {@code null}, the method searches across all
|
||||
* physical networks that have extensions registered and returns the capabilities for
|
||||
* the first matching extension.</p>
|
||||
*
|
||||
* @param physicalNetworkId physical network ID, or {@code null} for offering-level queries
|
||||
* @param providerName provider / extension name
|
||||
* @return capabilities map, or the default capabilities if no matching extension is found
|
||||
*/
|
||||
Map<Service, Map<Capability, String>> getNetworkCapabilitiesForProvider(Long physicalNetworkId, String providerName);
|
||||
|
||||
}
|
||||
|
||||
@ -24,7 +24,8 @@ import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
public interface ExtensionResourceMap extends InternalIdentity, Identity {
|
||||
enum ResourceType {
|
||||
Cluster
|
||||
Cluster,
|
||||
PhysicalNetwork
|
||||
}
|
||||
|
||||
long getExtensionId();
|
||||
|
||||
@ -0,0 +1,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);
|
||||
}
|
||||
73
api/src/test/java/com/cloud/network/NetworkTest.java
Normal file
73
api/src/test/java/com/cloud/network/NetworkTest.java
Normal 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"));
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
|
||||
@ -142,6 +142,8 @@ public interface NetworkOrchestrationService {
|
||||
boolean errorIfAlreadySetup, Long domainId, ACLType aclType, Boolean subdomainAccess, Long vpcId, Boolean isDisplayNetworkEnabled)
|
||||
throws ConcurrentOperationException;
|
||||
|
||||
boolean isIsolationMethodNetworkExtension(Long networkOfferingId);
|
||||
|
||||
void allocate(VirtualMachineProfile vm, LinkedHashMap<? extends Network, List<? extends NicProfile>> networks, Map<String, Map<Integer, String>> extraDhcpOptions) throws InsufficientCapacityException,
|
||||
ConcurrentOperationException;
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -313,6 +317,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
||||
@Inject
|
||||
NetworkOfferingDetailsDao _ntwkOffDetailsDao;
|
||||
@Inject
|
||||
NetworkOfferingServiceMapDao networkOfferingServiceMapDao;
|
||||
@Inject
|
||||
AccountGuestVlanMapDao _accountGuestVlanMapDao;
|
||||
@Inject
|
||||
DataCenterVnetDao _datacenterVnetDao;
|
||||
@ -368,6 +374,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 +471,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>
|
||||
*/
|
||||
protected List<NetworkElement> getNetworkElementsIncludingExtensions() {
|
||||
List<Extension> extensions = extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator);
|
||||
if (CollectionUtils.isEmpty(extensions)) {
|
||||
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;
|
||||
@ -776,7 +808,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
||||
|
||||
long related = -1;
|
||||
|
||||
for (final NetworkGuru guru : networkGurus) {
|
||||
for (final NetworkGuru guru : getDesignNetworkGurus(offering)) {
|
||||
final Network network = guru.design(offering, plan, predefined, name, vpcId, owner);
|
||||
if (network == null) {
|
||||
continue;
|
||||
@ -852,6 +884,46 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
||||
}
|
||||
}
|
||||
|
||||
private List<NetworkGuru> getDesignNetworkGurus(final NetworkOffering offering) {
|
||||
if (!isIsolationMethodNetworkExtension(offering.getId())) {
|
||||
return networkGurus;
|
||||
}
|
||||
|
||||
List<NetworkGuru> extensionGurus = networkGurus.stream()
|
||||
.filter(guru -> ExtensionHelper.NETWORK_EXTENSION_GURU_NAME.equals(guru.getName()))
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(extensionGurus)) {
|
||||
return extensionGurus;
|
||||
}
|
||||
|
||||
logger.warn("Network offering {} requests {}={}, but {} is not registered; using default guru ordering",
|
||||
offering.getUuid(), ExtensionHelper.NETWORK_ISOLATION_METHOD_DETAIL_KEY,
|
||||
ExtensionHelper.NETWORK_EXTENSION_ISOLATION_METHOD, ExtensionHelper.NETWORK_EXTENSION_GURU_NAME);
|
||||
return networkGurus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIsolationMethodNetworkExtension(final Long networkOfferingId) {
|
||||
if (networkOfferingId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> providers = networkOfferingServiceMapDao.getDistinctProviders(networkOfferingId);
|
||||
if (CollectionUtils.isEmpty(providers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String providerName : providers) {
|
||||
if (!extensionHelper.isNetworkExtensionProvider(providerName)) {
|
||||
continue;
|
||||
}
|
||||
if (extensionHelper.usesNetworkExtensionIsolation(providerName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static NetworkVO getNetworkVO(long id, final NetworkOffering offering, final DeploymentPlan plan, final Network predefined,
|
||||
Network network, final NetworkGuru guru, final Account owner,
|
||||
@ -1593,6 +1665,9 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
||||
// implement network elements and re-apply all the network rules
|
||||
implementNetworkElementsAndResources(dest, context, network, offering);
|
||||
|
||||
// reload network after implementing the network
|
||||
network = _networksDao.findById(networkId);
|
||||
|
||||
long dcId = dest.getDataCenter().getId();
|
||||
if (networkMeetsPersistenceCriteria(network, offering, false)) {
|
||||
setupPersistentNetwork(network, offering, dcId);
|
||||
@ -1686,7 +1761,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
||||
}
|
||||
}
|
||||
|
||||
for (final NetworkElement element : networkElements) {
|
||||
List<NetworkElement> allNetworkElements = getNetworkElementsIncludingExtensions();
|
||||
for (final NetworkElement element : allNetworkElements) {
|
||||
if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) {
|
||||
((AggregatedCommandExecutor) element).prepareAggregatedExecution(network, dest);
|
||||
}
|
||||
@ -1703,7 +1779,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 : allNetworkElements) {
|
||||
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 +1793,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
||||
}
|
||||
reconfigureAndApplyStaticRouteForVpcVpn(network);
|
||||
} finally {
|
||||
for (final NetworkElement element : networkElements) {
|
||||
for (final NetworkElement element : allNetworkElements) {
|
||||
if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) {
|
||||
((AggregatedCommandExecutor) element).cleanupAggregatedExecution(network, dest);
|
||||
}
|
||||
@ -1738,7 +1814,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 +2107,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 +2120,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 +2151,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 +2178,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 +2306,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 +2361,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 +2405,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 +2487,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 +2523,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 +2610,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 +2673,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 +3404,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 +3515,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 +3886,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: "
|
||||
@ -3827,6 +3903,17 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vmProfile.getType() == Type.User && element.getProvider() != null) {
|
||||
if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns)
|
||||
&& _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, element.getProvider()) && element instanceof DnsServiceProvider) {
|
||||
final DnsServiceProvider sp = (DnsServiceProvider) element;
|
||||
try {
|
||||
sp.removeDnsEntry(network, nicProfile, vmProfile);
|
||||
} catch (ResourceUnavailableException e) {
|
||||
logger.error("Failed to remove dns entry due to: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3846,7 +3933,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 +3981,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 +4556,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 +4587,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 +4756,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 +5041,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);
|
||||
|
||||
@ -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>();
|
||||
@ -388,7 +398,7 @@ public class NetworkOrchestratorTest extends TestCase {
|
||||
when(testOrchestrator._ipAddressDao.findByIpAndSourceNetworkId(Mockito.anyLong(), Mockito.anyString())).thenReturn(ipVoSpy);
|
||||
}
|
||||
|
||||
VlanVO vlanSpy = Mockito.spy(new VlanVO(Vlan.VlanType.DirectAttached, "vlanTag", vlanGateway, vlanNetmask, 0l, "192.168.100.100 - 192.168.100.200", 0l, new Long(0l),
|
||||
VlanVO vlanSpy = Mockito.spy(new VlanVO(Vlan.VlanType.DirectAttached, "vlanTag", vlanGateway, vlanNetmask, 0l, "192.168.100.100 - 192.168.100.200", 0l, 0l,
|
||||
"ip6Gateway", "ip6Cidr", "ip6Range"));
|
||||
|
||||
Mockito.doReturn(0l).when(vlanSpy).getId();
|
||||
@ -1010,4 +1020,57 @@ 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;
|
||||
|
||||
List<NetworkElement> result = 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));
|
||||
|
||||
List<NetworkElement> result = 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));
|
||||
|
||||
List<NetworkElement> result = testOrchestrator.getNetworkElementsIncludingExtensions();
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,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;
|
||||
@ -392,7 +391,7 @@ public class NetworkDaoImpl extends GenericDaoBase<NetworkVO, Long>implements Ne
|
||||
final TransactionLegacy txn = TransactionLegacy.currentTxn();
|
||||
txn.start();
|
||||
for (final String service : serviceProviderMap.keySet()) {
|
||||
final NetworkServiceMapVO serviceMap = new NetworkServiceMapVO(networkId, Service.getService(service), Provider.getProvider(serviceProviderMap.get(service)));
|
||||
final NetworkServiceMapVO serviceMap = new NetworkServiceMapVO(networkId, Service.getService(service).getName(), serviceProviderMap.get(service));
|
||||
_ntwkSvcMap.persist(serviceMap);
|
||||
}
|
||||
txn.commit();
|
||||
|
||||
@ -81,6 +81,12 @@ public class NetworkServiceMapVO implements InternalIdentity {
|
||||
this.provider = provider.getName();
|
||||
}
|
||||
|
||||
public NetworkServiceMapVO(long networkId, String serviceName, String providerName) {
|
||||
this.networkId = networkId;
|
||||
this.service = serviceName;
|
||||
this.provider = providerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder("[Network Service[");
|
||||
|
||||
@ -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
|
||||
@ -301,21 +305,27 @@ public class PhysicalNetworkServiceProviderVO implements PhysicalNetworkServiceP
|
||||
if (this.isLbServiceProvided()) {
|
||||
services.add(Service.Lb);
|
||||
}
|
||||
if (this.sourcenatServiceProvided) {
|
||||
if (this.isSourcenatServiceProvided()) {
|
||||
services.add(Service.SourceNat);
|
||||
}
|
||||
if (this.staticnatServiceProvided) {
|
||||
if (this.isStaticnatServiceProvided()) {
|
||||
services.add(Service.StaticNat);
|
||||
}
|
||||
if (this.portForwardingServiceProvided) {
|
||||
if (this.isPortForwardingServiceProvided()) {
|
||||
services.add(Service.PortForwarding);
|
||||
}
|
||||
if (this.isNetworkAclServiceProvided()) {
|
||||
services.add(Service.NetworkACL);
|
||||
}
|
||||
if (this.isUserdataServiceProvided()) {
|
||||
services.add(Service.UserData);
|
||||
}
|
||||
if (this.isSecuritygroupServiceProvided()) {
|
||||
services.add(Service.SecurityGroup);
|
||||
}
|
||||
if (this.isCustomActionServiceProvided()) {
|
||||
services.add(Service.CustomAction);
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
@ -327,4 +337,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,8 +25,6 @@ import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import com.cloud.network.Network.Provider;
|
||||
import com.cloud.network.Network.Service;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
|
||||
@Entity
|
||||
@ -72,10 +70,10 @@ public class VpcServiceMapVO {
|
||||
public VpcServiceMapVO() {
|
||||
}
|
||||
|
||||
public VpcServiceMapVO(long vpcId, Service service, Provider provider) {
|
||||
public VpcServiceMapVO(long vpcId, String serviceName, String providerName) {
|
||||
this.vpcId = vpcId;
|
||||
this.service = service.getName();
|
||||
this.provider = provider.getName();
|
||||
this.service = serviceName;
|
||||
this.provider = providerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -142,7 +142,7 @@ public class VpcDaoImpl extends GenericDaoBase<VpcVO, Long> implements VpcDao {
|
||||
txn.start();
|
||||
for (String service : serviceProviderMap.keySet()) {
|
||||
for (String provider : serviceProviderMap.get(service)) {
|
||||
VpcServiceMapVO serviceMap = new VpcServiceMapVO(vpcId, Network.Service.getService(service), Network.Provider.getProvider(provider));
|
||||
VpcServiceMapVO serviceMap = new VpcServiceMapVO(vpcId, Network.Service.getService(service).getName(), provider);
|
||||
_vpcSvcMap.persist(serviceMap);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,4 +42,6 @@ public interface VpcOfferingServiceMapDao extends GenericDao<VpcOfferingServiceM
|
||||
|
||||
List<VpcOfferingServiceMapVO> listProvidersForServiceForVpcOffering(long vpcOfferingId, Service service);
|
||||
|
||||
List<Long> listOfferingIdsByServiceAndProvider(Service service, String provider);
|
||||
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ public class VpcOfferingServiceMapDaoImpl extends GenericDaoBase<VpcOfferingServ
|
||||
final SearchBuilder<VpcOfferingServiceMapVO> AllFieldsSearch;
|
||||
final SearchBuilder<VpcOfferingServiceMapVO> MultipleServicesSearch;
|
||||
final GenericSearchBuilder<VpcOfferingServiceMapVO, String> ServicesSearch;
|
||||
final GenericSearchBuilder<VpcOfferingServiceMapVO, Long> OfferingIdsByServiceAndProviderSearch;
|
||||
|
||||
protected VpcOfferingServiceMapDaoImpl() {
|
||||
super();
|
||||
@ -56,6 +57,12 @@ public class VpcOfferingServiceMapDaoImpl extends GenericDaoBase<VpcOfferingServ
|
||||
ServicesSearch.and("offeringId", ServicesSearch.entity().getVpcOfferingId(), SearchCriteria.Op.EQ);
|
||||
ServicesSearch.select(null, Func.DISTINCT, ServicesSearch.entity().getService());
|
||||
ServicesSearch.done();
|
||||
|
||||
OfferingIdsByServiceAndProviderSearch = createSearchBuilder(Long.class);
|
||||
OfferingIdsByServiceAndProviderSearch.selectFields(OfferingIdsByServiceAndProviderSearch.entity().getVpcOfferingId());
|
||||
OfferingIdsByServiceAndProviderSearch.and("service", OfferingIdsByServiceAndProviderSearch.entity().getService(), SearchCriteria.Op.EQ);
|
||||
OfferingIdsByServiceAndProviderSearch.and("provider", OfferingIdsByServiceAndProviderSearch.entity().getProvider(), SearchCriteria.Op.EQ);
|
||||
OfferingIdsByServiceAndProviderSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -129,4 +136,12 @@ public class VpcOfferingServiceMapDaoImpl extends GenericDaoBase<VpcOfferingServ
|
||||
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listOfferingIdsByServiceAndProvider(Service service, String provider) {
|
||||
SearchCriteria<Long> sc = OfferingIdsByServiceAndProviderSearch.create();
|
||||
sc.setParameters("service", service.getName());
|
||||
sc.setParameters("provider", provider);
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,4 +44,6 @@ public interface NetworkOfferingServiceMapDao extends GenericDao<NetworkOffering
|
||||
List<String> listServicesForNetworkOffering(long networkOfferingId);
|
||||
|
||||
List<String> getDistinctProviders(long offId);
|
||||
|
||||
List<Long> listOfferingIdsByServiceAndProvider(Service service, String provider);
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ public class NetworkOfferingServiceMapDaoImpl extends GenericDaoBase<NetworkOffe
|
||||
final GenericSearchBuilder<NetworkOfferingServiceMapVO, String> ProvidersSearch;
|
||||
final GenericSearchBuilder<NetworkOfferingServiceMapVO, String> ServicesSearch;
|
||||
final GenericSearchBuilder<NetworkOfferingServiceMapVO, String> DistinctProvidersSearch;
|
||||
final GenericSearchBuilder<NetworkOfferingServiceMapVO, Long> OfferingIdsByServiceAndProviderSearch;
|
||||
|
||||
protected NetworkOfferingServiceMapDaoImpl() {
|
||||
super();
|
||||
@ -71,6 +72,12 @@ public class NetworkOfferingServiceMapDaoImpl extends GenericDaoBase<NetworkOffe
|
||||
DistinctProvidersSearch.and("provider", DistinctProvidersSearch.entity().getProvider(), SearchCriteria.Op.EQ);
|
||||
DistinctProvidersSearch.selectFields(DistinctProvidersSearch.entity().getProvider());
|
||||
DistinctProvidersSearch.done();
|
||||
|
||||
OfferingIdsByServiceAndProviderSearch = createSearchBuilder(Long.class);
|
||||
OfferingIdsByServiceAndProviderSearch.selectFields(OfferingIdsByServiceAndProviderSearch.entity().getNetworkOfferingId());
|
||||
OfferingIdsByServiceAndProviderSearch.and("service", OfferingIdsByServiceAndProviderSearch.entity().getService(), SearchCriteria.Op.EQ);
|
||||
OfferingIdsByServiceAndProviderSearch.and("provider", OfferingIdsByServiceAndProviderSearch.entity().getProvider(), SearchCriteria.Op.EQ);
|
||||
OfferingIdsByServiceAndProviderSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -170,4 +177,12 @@ public class NetworkOfferingServiceMapDaoImpl extends GenericDaoBase<NetworkOffe
|
||||
List<String> results = customSearch(sc, null);
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listOfferingIdsByServiceAndProvider(Service service, String provider) {
|
||||
SearchCriteria<Long> sc = OfferingIdsByServiceAndProviderSearch.create();
|
||||
sc.setParameters("service", service.getName());
|
||||
sc.setParameters("provider", provider);
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -470,6 +470,13 @@ SELECT uuid(), role_id, 'quotaResourceStatement', permission, sort_order
|
||||
FROM cloud.role_permissions rp
|
||||
WHERE rule = 'quotaStatement' AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaResourceStatement');
|
||||
|
||||
-- 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" AFTER `networkacl_service_provided`');
|
||||
|
||||
--- Gui theme login base domain
|
||||
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.gui_themes', 'login_base_domain', 'TEXT DEFAULT NULL');
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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,8 @@ import com.cloud.utils.db.GenericDao;
|
||||
public interface ExtensionDao extends GenericDao<ExtensionVO, Long> {
|
||||
|
||||
ExtensionVO findByName(String name);
|
||||
|
||||
List<ExtensionVO> listByType(Extension.Type type);
|
||||
|
||||
ExtensionVO findByNameAndType(String name, Extension.Type type);
|
||||
}
|
||||
|
||||
@ -17,6 +17,9 @@
|
||||
|
||||
package org.apache.cloudstack.framework.extensions.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.extension.Extension;
|
||||
import org.apache.cloudstack.framework.extensions.vo.ExtensionVO;
|
||||
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
@ -39,7 +42,21 @@ 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionVO findByNameAndType(String name, Extension.Type type) {
|
||||
SearchCriteria<ExtensionVO> sc = AllFieldSearch.create();
|
||||
sc.setParameters("name", name);
|
||||
sc.setParameters("type", type);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,5 +28,9 @@ public interface ExtensionResourceMapDao extends GenericDao<ExtensionResourceMap
|
||||
|
||||
ExtensionResourceMapVO findByResourceIdAndType(long resourceId, ExtensionResourceMap.ResourceType resourceType);
|
||||
|
||||
List<Long> listResourceIdsByExtensionIdAndType(long extensionId,ExtensionResourceMap.ResourceType resourceType);
|
||||
ExtensionResourceMapVO findResourceByExtensionIdAndResourceIdAndType(long extensionId, long resourceId, ExtensionResourceMap.ResourceType resourceType);
|
||||
|
||||
List<Long> listResourceIdsByExtensionIdAndType(long extensionId, ExtensionResourceMap.ResourceType resourceType);
|
||||
|
||||
List<Long> listResourceIdsByType(ExtensionResourceMap.ResourceType resourceType);
|
||||
}
|
||||
|
||||
@ -55,6 +55,17 @@ public class ExtensionResourceMapDaoImpl extends GenericDaoBase<ExtensionResourc
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ExtensionResourceMapVO findResourceByExtensionIdAndResourceIdAndType(long extensionId, long resourceId,
|
||||
ExtensionResourceMap.ResourceType resourceType) {
|
||||
SearchCriteria<ExtensionResourceMapVO> sc = genericSearch.create();
|
||||
sc.setParameters("extensionId", extensionId);
|
||||
sc.setParameters("resourceId", resourceId);
|
||||
sc.setParameters("resourceType", resourceType);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listResourceIdsByExtensionIdAndType(long extensionId, ExtensionResourceMap.ResourceType resourceType) {
|
||||
GenericSearchBuilder<ExtensionResourceMapVO, Long> sb = createSearchBuilder(Long.class);
|
||||
@ -67,4 +78,15 @@ public class ExtensionResourceMapDaoImpl extends GenericDaoBase<ExtensionResourc
|
||||
sc.setParameters("resourceType", resourceType);
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listResourceIdsByType(ExtensionResourceMap.ResourceType resourceType) {
|
||||
GenericSearchBuilder<ExtensionResourceMapVO, Long> sb = createSearchBuilder(Long.class);
|
||||
sb.selectFields(sb.entity().getResourceId());
|
||||
sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ);
|
||||
sb.done();
|
||||
SearchCriteria<Long> sc = sb.create();
|
||||
sc.setParameters("resourceType", resourceType);
|
||||
return customSearch(sc, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ import org.apache.cloudstack.framework.extensions.api.RunCustomActionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UnregisterExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateCustomActionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.api.UpdateRegisteredExtensionCmd;
|
||||
import org.apache.cloudstack.framework.extensions.command.ExtensionServerActionBaseCommand;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
@ -65,6 +66,8 @@ public interface ExtensionsManager extends Manager {
|
||||
|
||||
Extension unregisterExtensionWithResource(UnregisterExtensionCmd cmd);
|
||||
|
||||
Extension updateRegisteredExtensionWithResource(UpdateRegisteredExtensionCmd cmd);
|
||||
|
||||
Extension updateExtension(UpdateExtensionCmd cmd);
|
||||
|
||||
Extension registerExtensionWithResource(RegisterExtensionCmd cmd);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -40,7 +40,7 @@ public class ExtensionDetailsVO implements ResourceDetail {
|
||||
@Column(name = "name", nullable = false, length = 255)
|
||||
private String name;
|
||||
|
||||
@Column(name = "value", nullable = false, length = 255)
|
||||
@Column(name = "value", nullable = false, length = 4096)
|
||||
private String value;
|
||||
|
||||
@Column(name = "display")
|
||||
|
||||
@ -40,7 +40,7 @@ public class ExtensionResourceMapDetailsVO implements ResourceDetail {
|
||||
@Column(name = "name", nullable = false, length = 255)
|
||||
private String name;
|
||||
|
||||
@Column(name = "value", nullable = false, length = 255)
|
||||
@Column(name = "value", nullable = false, length = 4096)
|
||||
private String value;
|
||||
|
||||
@Column(name = "display")
|
||||
|
||||
@ -33,4 +33,7 @@
|
||||
<bean id="ExtensionsManager" class="org.apache.cloudstack.framework.extensions.manager.ExtensionsManagerImpl" />
|
||||
<bean id="ExtensionCustomActionDaoImpl" class="org.apache.cloudstack.framework.extensions.dao.ExtensionCustomActionDaoImpl" />
|
||||
<bean id="ExtensionCustomActionDetailsDaoImpl" class="org.apache.cloudstack.framework.extensions.dao.ExtensionCustomActionDetailsDaoImpl" />
|
||||
<bean id="networkExtension" class="org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement">
|
||||
<property name="name" value="NetworkExtension" />
|
||||
</bean>
|
||||
</beans>
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,4 +83,52 @@ public class ExtensionResourceMapDaoImplTest {
|
||||
when(dao.listResourceIdsByExtensionIdAndType(1L, ExtensionResourceMap.ResourceType.Cluster)).thenReturn(expectedIds);
|
||||
assertEquals(expectedIds, dao.listResourceIdsByExtensionIdAndType(1L, ExtensionResourceMap.ResourceType.Cluster));
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tests for new methods: findResourceByExtensionIdAndResourceIdAndType, listResourceIdsByType
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
public void findResourceByExtensionIdAndResourceIdAndTypeReturnsNullWhenNoMatch() {
|
||||
when(dao.findResourceByExtensionIdAndResourceIdAndType(999L, 77L, ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(null);
|
||||
assertNull(dao.findResourceByExtensionIdAndResourceIdAndType(999L, 77L, ExtensionResourceMap.ResourceType.PhysicalNetwork));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findResourceByExtensionIdAndResourceIdAndTypeReturnsMatchingEntry() {
|
||||
ExtensionResourceMapVO map1 = new ExtensionResourceMapVO();
|
||||
map1.setExtensionId(42L);
|
||||
map1.setResourceId(7L);
|
||||
map1.setResourceType(ExtensionResourceMap.ResourceType.PhysicalNetwork);
|
||||
ExtensionResourceMapVO expected = map1;
|
||||
when(dao.findResourceByExtensionIdAndResourceIdAndType(42L, 7L, ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(expected);
|
||||
ExtensionResourceMapVO result = dao.findResourceByExtensionIdAndResourceIdAndType(42L, 7L, ExtensionResourceMap.ResourceType.PhysicalNetwork);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findResourceByExtensionIdAndResourceIdAndTypeDifferentiatesResourceTypes() {
|
||||
ExtensionResourceMapVO clusterMap = new ExtensionResourceMapVO();
|
||||
clusterMap.setResourceType(ExtensionResourceMap.ResourceType.Cluster);
|
||||
when(dao.findResourceByExtensionIdAndResourceIdAndType(10L, 55L, ExtensionResourceMap.ResourceType.Cluster)).thenReturn(clusterMap);
|
||||
when(dao.findResourceByExtensionIdAndResourceIdAndType(10L, 55L, ExtensionResourceMap.ResourceType.PhysicalNetwork)).thenReturn(null);
|
||||
|
||||
assertEquals(clusterMap, dao.findResourceByExtensionIdAndResourceIdAndType(10L, 55L, ExtensionResourceMap.ResourceType.Cluster));
|
||||
assertNull(dao.findResourceByExtensionIdAndResourceIdAndType(10L, 55L, ExtensionResourceMap.ResourceType.PhysicalNetwork));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1573,6 +1573,10 @@ public class ApiDBUtils {
|
||||
return s_networkModel.canElementEnableIndividualServices(serviceProvider);
|
||||
}
|
||||
|
||||
public static boolean canElementEnableIndividualServicesByName(String providerName) {
|
||||
return s_networkModel.canElementEnableIndividualServicesByName(providerName);
|
||||
}
|
||||
|
||||
public static Pair<Long, Boolean> getDomainNetworkDetails(long networkId) {
|
||||
NetworkDomainVO map = s_networkDomainDao.getDomainNetworkMapByNetworkId(networkId);
|
||||
|
||||
|
||||
@ -548,6 +548,8 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
|
||||
ResourceIconManager resourceIconManager;
|
||||
@Inject
|
||||
AsyncJobDao asyncJobDao;
|
||||
@Inject
|
||||
NetworkModel networkModel;
|
||||
|
||||
|
||||
public static String getPrettyDomainPath(String path) {
|
||||
@ -3316,9 +3318,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;
|
||||
|
||||
@ -7308,7 +7308,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);
|
||||
}
|
||||
@ -7994,7 +7994,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
|
||||
// 1) Vaidate the detail values - have to match the lb provider
|
||||
// name
|
||||
final String providerStr = details.get(detail);
|
||||
if (Network.Provider.getProvider(providerStr) == null) {
|
||||
if (_networkModel.resolveProvider(providerStr) == null) {
|
||||
throw new InvalidParameterValueException("Invalid value " + providerStr + " for the detail " + detail);
|
||||
}
|
||||
if (serviceProviderMap.get(Service.Lb) != null) {
|
||||
|
||||
@ -99,6 +99,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
|
||||
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,98 @@ 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();
|
||||
List<Extension> extensions = extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator);
|
||||
if (CollectionUtils.isNotEmpty(nsps)) {
|
||||
Set<String> extensionProviderNames = new HashSet<>();
|
||||
if (extensions != null) {
|
||||
for (Extension extension : extensions) {
|
||||
if (extension == null || StringUtils.isBlank(extension.getName())) {
|
||||
continue;
|
||||
}
|
||||
extensionProviderNames.add(extension.getName());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Check if the network extension provider supports the service
|
||||
NetworkElement element = getElementImplementingProvider(provName);
|
||||
if (element == null) {
|
||||
continue;
|
||||
}
|
||||
Map<Service, Map<Capability, String>> elementCapabilities = element.getCapabilities();
|
||||
if (elementCapabilities == null || !elementCapabilities.containsKey(service)) {
|
||||
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 +1739,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 +1785,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 +1860,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 +1905,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 +2189,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 +2491,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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2975,7 +2975,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
|
||||
throw new InvalidParameterValueException("Network is not in the right state to be restarted. Correct states are: " + Network.State.Implemented + ", " + Network.State.Setup);
|
||||
}
|
||||
|
||||
if (network.getBroadcastDomainType() == BroadcastDomainType.Lswitch) {
|
||||
if (network.getBroadcastDomainType() == BroadcastDomainType.Lswitch && !_networkMgr.isIsolationMethodNetworkExtension(network.getNetworkOfferingId())) {
|
||||
/**
|
||||
* Unable to restart these networks now.
|
||||
* TODO Restarting a SDN based network requires updating the nics and the configuration
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -1482,7 +1482,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");
|
||||
}
|
||||
@ -1551,7 +1551,7 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
|
||||
}
|
||||
String providerStr = cmd.getProvider();
|
||||
if (providerStr != null) {
|
||||
Network.Provider provider = Network.Provider.getProvider(providerStr);
|
||||
Network.Provider provider = networkModel.resolveProvider(providerStr);
|
||||
if (provider == null) {
|
||||
throw new InvalidParameterValueException("The Provider " + providerStr + " does not exist; Unable to list Counter");
|
||||
}
|
||||
|
||||
@ -69,9 +69,11 @@ import com.cloud.network.dao.FirewallRulesDcidrsDao;
|
||||
import com.cloud.network.dao.IPAddressDao;
|
||||
import com.cloud.network.dao.IPAddressVO;
|
||||
import com.cloud.network.dao.NetworkDao;
|
||||
import com.cloud.network.dao.NetworkServiceMapDao;
|
||||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.network.element.FirewallServiceProvider;
|
||||
import com.cloud.network.element.NetworkACLServiceProvider;
|
||||
import com.cloud.network.element.NetworkElement;
|
||||
import com.cloud.network.element.PortForwardingServiceProvider;
|
||||
import com.cloud.network.element.StaticNatServiceProvider;
|
||||
import com.cloud.network.rules.FirewallManager;
|
||||
@ -150,6 +152,9 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
|
||||
EntityManager entityManager;
|
||||
@Inject
|
||||
NsxProviderDao nsxProviderDao;
|
||||
@Inject
|
||||
NetworkServiceMapDao networkServiceMapDao;
|
||||
|
||||
List<FirewallServiceProvider> _firewallElements;
|
||||
|
||||
List<PortForwardingServiceProvider> _pfElements;
|
||||
@ -617,13 +622,26 @@ 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) {
|
||||
supportedProtocols = caps.get(Capability.SupportedEgressProtocols).toLowerCase();
|
||||
// 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 = supportedProtocolsStr.toLowerCase();
|
||||
} else {
|
||||
supportedProtocols = caps.get(Capability.SupportedProtocols).toLowerCase();
|
||||
String supportedProtocolsStr = caps.get(Capability.SupportedProtocols);
|
||||
if (supportedProtocolsStr == null) {
|
||||
throw new CloudRuntimeException("Supported protocols capability is not defined for " + purpose + " service");
|
||||
}
|
||||
supportedProtocols = supportedProtocolsStr.toLowerCase();
|
||||
}
|
||||
|
||||
if (!supportedProtocols.contains(proto.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);
|
||||
}
|
||||
|
||||
@ -0,0 +1,162 @@
|
||||
// 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.guru;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.dc.DataCenter;
|
||||
import com.cloud.deploy.DeployDestination;
|
||||
import com.cloud.deploy.DeploymentPlan;
|
||||
import com.cloud.exception.InsufficientAddressCapacityException;
|
||||
import com.cloud.exception.InsufficientVirtualNetworkCapacityException;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.Network.GuestType;
|
||||
import com.cloud.network.Network.State;
|
||||
import com.cloud.network.PhysicalNetwork;
|
||||
import com.cloud.network.dao.NetworkVO;
|
||||
import com.cloud.offering.NetworkOffering;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.vm.NicProfile;
|
||||
import com.cloud.vm.ReservationContext;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Guest network guru for extension-backed providers on physical networks whose
|
||||
* isolation method is set to "Extension".
|
||||
*
|
||||
* <p>For <b>isolated</b> networks the allocation path is identical to
|
||||
* {@link GuestNetworkGuru}: a virtual CIDR is managed internally and IPs are
|
||||
* taken from that pool.</p>
|
||||
*
|
||||
* <p>For <b>shared</b> networks (upstream router as gateway) the
|
||||
* allocation is delegated entirely to {@link DirectNetworkGuru}</p>
|
||||
*/
|
||||
public class NetworkExtensionGuestNetworkGuru extends GuestNetworkGuru {
|
||||
|
||||
@Inject
|
||||
protected ExtensionHelper extensionHelper;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("DirectNetworkGuru")
|
||||
protected DirectNetworkGuru directNetworkGuru;
|
||||
|
||||
@Override
|
||||
protected boolean canHandle(NetworkOffering offering, DataCenter.NetworkType networkType, PhysicalNetwork physicalNetwork) {
|
||||
List<String> providers = networkOfferingServiceMapDao.getDistinctProviders(offering.getId());
|
||||
if (CollectionUtils.isEmpty(providers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return providers.stream().anyMatch(providerName ->
|
||||
extensionHelper.isNetworkExtensionProvider(providerName)
|
||||
&& physicalNetwork != null
|
||||
&& extensionHelper.usesNetworkExtensionIsolation(providerName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Designs the network and ensures the state is {@code Allocated}, never {@code Setup}.
|
||||
*
|
||||
* <p>{@link GuestNetworkGuru#design} sets the state to {@code Setup} when
|
||||
* {@code offering.isSpecifyVlan() && !offering.isPersistent()}, which means the
|
||||
* parent considers the physical network already in place and skips
|
||||
* {@code element.implement()} later.
|
||||
* For NetworkExtension-based networks Resetting the state to
|
||||
* {@code Allocated} here guarantees {@code implement-network} is always called.</p>
|
||||
*/
|
||||
@Override
|
||||
public Network design(final NetworkOffering offering, final DeploymentPlan plan,
|
||||
final Network userSpecified, final String name,
|
||||
final Long vpcId, final Account owner) {
|
||||
final Network network = super.design(offering, plan, userSpecified, name, vpcId, owner);
|
||||
if (network == null) {
|
||||
return null;
|
||||
}
|
||||
if (network instanceof NetworkVO && network.getState() == State.Setup) {
|
||||
NetworkVO networkVO = (NetworkVO) network;
|
||||
networkVO.setState(State.Allocated);
|
||||
return networkVO;
|
||||
}
|
||||
return network;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Network implement(final Network network, final NetworkOffering offering, final DeployDestination dest, final ReservationContext context)
|
||||
throws InsufficientVirtualNetworkCapacityException {
|
||||
assert network.getState() == Network.State.Implementing : "Why are we implementing " + network;
|
||||
|
||||
final long dcId = dest.getDataCenter().getId();
|
||||
|
||||
// physical network id can be null in Guest Network in Basic zone
|
||||
Long physicalNetworkId = network.getPhysicalNetworkId();
|
||||
if (physicalNetworkId == null) {
|
||||
physicalNetworkId = _networkModel.findPhysicalNetworkId(dcId, offering.getTags(), offering.getTrafficType());
|
||||
}
|
||||
|
||||
// The broadcast_domain_type and broadcast_uri are set by the extension script output
|
||||
final NetworkVO implemented = getNetworkVO(network, offering, physicalNetworkId);
|
||||
return implemented;
|
||||
}
|
||||
|
||||
private static NetworkVO getNetworkVO(Network network, NetworkOffering offering, Long physicalNetworkId) {
|
||||
final NetworkVO implemented =
|
||||
new NetworkVO(network.getTrafficType(), network.getMode(), network.getBroadcastDomainType(), network.getNetworkOfferingId(), Network.State.Allocated,
|
||||
network.getDataCenterId(), physicalNetworkId, offering.isRedundantRouter());
|
||||
|
||||
if (network.getGateway() != null) {
|
||||
implemented.setGateway(network.getGateway());
|
||||
}
|
||||
if (network.getCidr() != null) {
|
||||
implemented.setCidr(network.getCidr());
|
||||
}
|
||||
if (network.getBroadcastUri() != null) {
|
||||
implemented.setBroadcastUri(network.getBroadcastUri());
|
||||
}
|
||||
return implemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a NIC for the given network.
|
||||
*
|
||||
* <p>For <b>shared</b> networks the call is forwarded to
|
||||
* {@link DirectNetworkGuru#allocate}, which handles the
|
||||
* {@code user_ip_address} lookup, transaction wrapping, and MAC assignment
|
||||
* identically to how direct/shared networks work elsewhere in CloudStack.</p>
|
||||
*
|
||||
* <p>For <b>isolated</b> networks the standard
|
||||
* {@link GuestNetworkGuru#allocate} path is used unchanged.</p>
|
||||
*/
|
||||
@Override
|
||||
public NicProfile allocate(final Network network, final NicProfile nic, final VirtualMachineProfile vm)
|
||||
throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
|
||||
|
||||
if (network.getGuestType() == GuestType.Shared) {
|
||||
// Delegate entirely to DirectNetworkGuru
|
||||
return directNetworkGuru.allocate(network, nic, vm);
|
||||
}
|
||||
|
||||
// Isolated network: use the standard GuestNetworkGuru CIDR pool allocation.
|
||||
return super.allocate(network, nic, vm);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -2055,6 +2056,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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,9 @@
|
||||
<bean id="ExternalGuestNetworkGuru" class="com.cloud.network.guru.ExternalGuestNetworkGuru">
|
||||
<property name="name" value="ExternalGuestNetworkGuru" />
|
||||
</bean>
|
||||
<bean id="NetworkExtensionGuestNetworkGuru" class="com.cloud.network.guru.NetworkExtensionGuestNetworkGuru">
|
||||
<property name="name" value="NetworkExtensionGuestNetworkGuru" />
|
||||
</bean>
|
||||
<bean id="PublicNetworkGuru" class="com.cloud.network.guru.PublicNetworkGuru">
|
||||
<property name="name" value="PublicNetworkGuru" />
|
||||
</bean>
|
||||
|
||||
@ -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[])
|
||||
*/
|
||||
|
||||
@ -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;
|
||||
networkModel.extensionHelper = extensionHelper;
|
||||
networkModel.networkExtensionElement = networkExtensionElement;
|
||||
networkModel._physicalNetworkDao = physicalNetworkDao;
|
||||
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,158 @@ 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 listSupportedNetworkServiceProvidersIncludesExtensionBackedProvidersWhenServiceIsSupported() {
|
||||
PhysicalNetworkServiceProviderVO nsp = mock(PhysicalNetworkServiceProviderVO.class);
|
||||
when(nsp.getProviderName()).thenReturn("my-ext");
|
||||
when(nsp.isFirewallServiceProvided()).thenReturn(true);
|
||||
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<>());
|
||||
|
||||
NetworkExtensionElement mockElement = mock(NetworkExtensionElement.class);
|
||||
Map<Network.Service, Map<Network.Capability, String>> capabilities = new HashMap<>();
|
||||
capabilities.put(Network.Service.Firewall, new HashMap<>());
|
||||
when(networkExtensionElement.withProviderName("my-ext")).thenReturn(mockElement);
|
||||
when(mockElement.getCapabilities()).thenReturn(capabilities);
|
||||
|
||||
List<? extends Network.Provider> result =
|
||||
networkModel.listSupportedNetworkServiceProviders(Network.Service.Firewall.getName());
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void listSupportedNetworkServiceProvidersExcludesExtensionBackedProvidersWhenServiceIsNotSupported() {
|
||||
PhysicalNetworkServiceProviderVO nsp = mock(PhysicalNetworkServiceProviderVO.class);
|
||||
when(nsp.getProviderName()).thenReturn("my-ext");
|
||||
when(nsp.isFirewallServiceProvided()).thenReturn(true);
|
||||
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<>());
|
||||
|
||||
NetworkExtensionElement mockElement = mock(NetworkExtensionElement.class);
|
||||
Map<Network.Service, Map<Network.Capability, String>> capabilities = new HashMap<>();
|
||||
when(networkExtensionElement.withProviderName("my-ext")).thenReturn(mockElement);
|
||||
when(mockElement.getCapabilities()).thenReturn(capabilities);
|
||||
|
||||
List<? extends Network.Provider> result =
|
||||
networkModel.listSupportedNetworkServiceProviders(Network.Service.Firewall.getName());
|
||||
|
||||
boolean found = result.stream().anyMatch(p -> "my-ext".equalsIgnoreCase(p.getName()));
|
||||
assertFalse("Extension-backed provider should NOT be included", found);
|
||||
|
||||
Mockito.verify(physicalNetworkServiceProviderDao, Mockito.times(1)).listAll();
|
||||
Mockito.verify(physicalNetworkServiceProviderDao, Mockito.never()).listBy(Mockito.anyLong());
|
||||
}
|
||||
}
|
||||
|
||||
@ -427,6 +427,8 @@ public class AutoScaleManagerImplTest {
|
||||
when(conditionDao.findById(any())).thenReturn(conditionMock);
|
||||
when(conditionDao.persist(any(ConditionVO.class))).thenReturn(conditionMock);
|
||||
|
||||
when(networkModel.resolveProvider(counterProvider)).thenReturn(Network.Provider.VirtualRouter);
|
||||
|
||||
when(accountManager.finalizeOwner(nullable(Account.class), nullable(String.class), nullable(Long.class), nullable(Long.class))).thenReturn(account);
|
||||
Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any());
|
||||
|
||||
|
||||
@ -62,6 +62,8 @@ import com.cloud.vm.dao.VMInstanceDao;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
||||
@ -151,6 +153,7 @@ public class ConfigDriveNetworkElementTest {
|
||||
@Mock private CallContext callContextMock;
|
||||
@Mock private DomainVO domainVO;
|
||||
@Mock private NetworkOrchestrationService _networkOrchestrationService;
|
||||
@Mock private ExtensionHelper extensionHelper;
|
||||
|
||||
@Spy @InjectMocks
|
||||
private ConfigDriveNetworkElement _configDrivesNetworkElement = new ConfigDriveNetworkElement();
|
||||
@ -202,6 +205,8 @@ public class ConfigDriveNetworkElementTest {
|
||||
doReturn(_configDrivesNetworkElement.getProvider().getName()).when(_ntwkSrvcDao).getProviderForServiceInNetwork(NETWORK_ID, Network.Service.UserData);
|
||||
|
||||
_networkModel.setNetworkElements(Arrays.asList(_configDrivesNetworkElement));
|
||||
ReflectionTestUtils.setField(_networkModel, "extensionHelper", extensionHelper);
|
||||
Mockito.lenient().when(extensionHelper.isNetworkExtensionProvider(Mockito.anyString())).thenReturn(false);
|
||||
_networkModel.start();
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
// 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.guru;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cloudstack.extension.ExtensionHelper;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import com.cloud.dc.DataCenter.NetworkType;
|
||||
import com.cloud.network.Network;
|
||||
import com.cloud.network.Network.GuestType;
|
||||
import com.cloud.network.Networks.TrafficType;
|
||||
import com.cloud.network.dao.PhysicalNetworkVO;
|
||||
import com.cloud.offering.NetworkOffering;
|
||||
import com.cloud.offerings.dao.NetworkOfferingServiceMapDao;
|
||||
|
||||
public class NetworkExtensionGuestNetworkGuruTest {
|
||||
|
||||
private final NetworkExtensionGuestNetworkGuru guru = new NetworkExtensionGuestNetworkGuru();
|
||||
|
||||
@Mock
|
||||
private NetworkOffering offering;
|
||||
@Mock
|
||||
private PhysicalNetworkVO physicalNetwork;
|
||||
@Mock
|
||||
private NetworkOfferingServiceMapDao networkOfferingServiceMapDao;
|
||||
@Mock
|
||||
private ExtensionHelper extensionHelper;
|
||||
|
||||
private AutoCloseable closeable;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
closeable = MockitoAnnotations.openMocks(this);
|
||||
guru.networkOfferingServiceMapDao = networkOfferingServiceMapDao;
|
||||
guru.extensionHelper = extensionHelper;
|
||||
|
||||
when(offering.getId()).thenReturn(42L);
|
||||
when(offering.getTrafficType()).thenReturn(TrafficType.Guest);
|
||||
when(offering.getGuestType()).thenReturn(GuestType.Isolated);
|
||||
when(offering.isSystemOnly()).thenReturn(false);
|
||||
when(physicalNetwork.getId()).thenReturn(7L);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
closeable.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canHandleWhenOfferingUsesStaticNetworkExtensionProvider() {
|
||||
String provider = Network.Provider.NetworkExtension.getName();
|
||||
when(networkOfferingServiceMapDao.getDistinctProviders(offering.getId())).thenReturn(List.of(provider));
|
||||
when(extensionHelper.isNetworkExtensionProvider(provider)).thenReturn(true);
|
||||
when(extensionHelper.usesNetworkExtensionIsolation(provider)).thenReturn(true);
|
||||
|
||||
assertTrue(guru.canHandle(offering, NetworkType.Advanced, physicalNetwork));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canHandleWhenOfferingUsesDynamicExtensionProvider() {
|
||||
when(networkOfferingServiceMapDao.getDistinctProviders(offering.getId())).thenReturn(List.of("my-ext"));
|
||||
when(extensionHelper.isNetworkExtensionProvider("my-ext")).thenReturn(true);
|
||||
when(extensionHelper.usesNetworkExtensionIsolation("my-ext")).thenReturn(true);
|
||||
|
||||
assertTrue(guru.canHandle(offering, NetworkType.Advanced, physicalNetwork));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotHandleWhenIsolationMethodDetailIsNotExtension() {
|
||||
when(networkOfferingServiceMapDao.getDistinctProviders(offering.getId())).thenReturn(List.of("my-ext"));
|
||||
when(extensionHelper.isNetworkExtensionProvider("my-ext")).thenReturn(true);
|
||||
when(extensionHelper.usesNetworkExtensionIsolation("my-ext")).thenReturn(false);
|
||||
|
||||
assertFalse(guru.canHandle(offering, NetworkType.Advanced, physicalNetwork));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotHandleWhenNoExtensionProvidersArePresent() {
|
||||
when(networkOfferingServiceMapDao.getDistinctProviders(offering.getId()))
|
||||
.thenReturn(List.of(Network.Provider.VirtualRouter.getName()));
|
||||
when(extensionHelper.isNetworkExtensionProvider(Network.Provider.VirtualRouter.getName())).thenReturn(false);
|
||||
|
||||
assertFalse(guru.canHandle(offering, NetworkType.Advanced, physicalNetwork));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -585,6 +585,11 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIsolationMethodNetworkExtension(Long networkOfferingId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.cloud.network.NetworkManager#allocate(com.cloud.vm.VirtualMachineProfile, java.util.List)
|
||||
*/
|
||||
|
||||
@ -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[])
|
||||
*/
|
||||
|
||||
@ -73,6 +73,11 @@ public class MockVpcOfferingServiceMapDaoImpl extends GenericDaoBase<VpcOffering
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listOfferingIdsByServiceAndProvider(Service service, String provider) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VpcOfferingServiceMapVO persist(VpcOfferingServiceMapVO vo) {
|
||||
return vo;
|
||||
|
||||
3308
test/integration/smoke/test_network_extension_namespace.py
Normal file
3308
test/integration/smoke/test_network_extension_namespace.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,7 @@ setup(name="Marvin",
|
||||
"marvin.sandbox.basic"],
|
||||
license="LICENSE.txt",
|
||||
install_requires=[
|
||||
"mysql-connector-python <= 8.4.0",
|
||||
"mysql-connector-python >= 8.4.0",
|
||||
"requests >= 2.2.1",
|
||||
"paramiko >= 1.13.0",
|
||||
"nose >= 1.3.3",
|
||||
|
||||
@ -245,6 +245,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",
|
||||
@ -1142,6 +1143,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",
|
||||
@ -1801,6 +1803,7 @@
|
||||
"label.networkofferingdisplaytext": "Network Offering",
|
||||
"label.networkofferingid": "Network Offering",
|
||||
"label.networkofferingname": "Network Offering",
|
||||
"label.networkorchestrator": "Network Orchestrator",
|
||||
"label.networkrate": "Network rate (Mb/s)",
|
||||
"label.networkread": "Network read",
|
||||
"label.networks": "Networks",
|
||||
@ -4301,6 +4304,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",
|
||||
|
||||
@ -45,7 +45,7 @@ export default {
|
||||
return fields
|
||||
},
|
||||
details: ['name', 'description', 'id', 'type', 'details', 'path', 'pathready', 'isuserdefined', 'orchestratorrequirespreparevm', 'reservedresourcedetails', 'created'],
|
||||
filters: ['orchestrator'],
|
||||
filters: ['Orchestrator', 'NetworkOrchestrator'],
|
||||
tabs: [{
|
||||
name: 'details',
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
|
||||
|
||||
@ -94,7 +94,7 @@ export default {
|
||||
icon: 'edit-outlined',
|
||||
label: 'label.update.physical.network',
|
||||
dataView: true,
|
||||
args: ['vlan', 'tags']
|
||||
args: ['vlan', 'tags', 'externaldetails']
|
||||
},
|
||||
{
|
||||
api: 'addTrafficType',
|
||||
|
||||
@ -121,6 +121,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',
|
||||
@ -210,6 +218,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: 'associateDnsZoneToNetwork',
|
||||
icon: 'link-outlined',
|
||||
|
||||
@ -34,8 +34,25 @@
|
||||
api="listExtensions"
|
||||
:apiParams="extensionsApiParams"
|
||||
resourceType="extension"
|
||||
@change-option="updateResourceTypeByExtension"
|
||||
defaultIcon="appstore-add-outlined" />
|
||||
</a-form-item>
|
||||
<a-form-item name="resourcetype" ref="resourcetype">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.resourcetype')" :tooltip="apiParams.resourcetype.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="form.resourcetype"
|
||||
:placeholder="apiParams.resourcetype.description"
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option v-for="opt in resourceTypeOptions" :key="opt" :label="opt">
|
||||
{{ opt }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item name="name" ref="name">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
|
||||
@ -152,6 +169,7 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
roleTypes: [],
|
||||
resourceTypeOptions: ['VirtualMachine', 'Network', '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']
|
||||
|
||||
@ -162,7 +162,7 @@ export default {
|
||||
},
|
||||
fetchExtensionTypes () {
|
||||
this.extensionTypes = []
|
||||
const extensionTypesList = ['Orchestrator']
|
||||
const extensionTypesList = ['Orchestrator', 'NetworkOrchestrator']
|
||||
extensionTypesList.forEach((item) => {
|
||||
this.extensionTypes.push({
|
||||
id: item,
|
||||
|
||||
@ -34,6 +34,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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
268
ui/src/views/extension/JsonViewer.vue
Normal file
268
ui/src/views/extension/JsonViewer.vue
Normal file
@ -0,0 +1,268 @@
|
||||
// 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.
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, h, provide, inject, watch } from 'vue'
|
||||
|
||||
const INDENT = 18 // px per depth level
|
||||
|
||||
const C = {
|
||||
key: '#0451a5',
|
||||
string: '#a31515',
|
||||
number: '#098658',
|
||||
boolean: '#0000ff',
|
||||
null: '#767676',
|
||||
punct: '#333333',
|
||||
arrow: '#888888',
|
||||
summary: '#888888'
|
||||
}
|
||||
|
||||
const JsonNode = defineComponent({
|
||||
name: 'JsonNode',
|
||||
props: {
|
||||
data: {},
|
||||
keyName: { type: String, default: null },
|
||||
depth: { type: Number, default: 0 },
|
||||
isLast: { type: Boolean, default: true }
|
||||
},
|
||||
setup (props) {
|
||||
// jeExpandMode tracks the last toolbar action so newly-mounted children
|
||||
// (revealed when their parent expands) pick up the correct initial state.
|
||||
const expandMode = inject('jeExpandMode', ref(null)) // null | 'expanded' | 'collapsed'
|
||||
const collapsed = ref(expandMode.value === 'expanded' ? false : props.depth >= 2)
|
||||
const toggle = () => { collapsed.value = !collapsed.value }
|
||||
|
||||
const expandSignal = inject('jeExpand', ref(0))
|
||||
const collapseSignal = inject('jeCollapse', ref(0))
|
||||
watch(expandSignal, () => { collapsed.value = false })
|
||||
watch(collapseSignal, () => { collapsed.value = props.depth >= 2 })
|
||||
|
||||
return () => {
|
||||
const { data, keyName, depth, isLast } = props
|
||||
const pl = `${depth * INDENT}px`
|
||||
|
||||
// Key fragment: "name":·
|
||||
const keyFrag = keyName !== null
|
||||
? [
|
||||
h('span', { style: { color: C.key } }, `"${keyName}"`),
|
||||
h('span', { style: { color: C.punct } }, ': ')
|
||||
]
|
||||
: []
|
||||
|
||||
const comma = !isLast ? h('span', { style: { color: C.punct } }, ',') : null
|
||||
|
||||
// null
|
||||
if (data === null) {
|
||||
return h('div', { style: { paddingLeft: pl } }, [
|
||||
...keyFrag,
|
||||
h('span', { style: { color: C.null } }, 'null'),
|
||||
comma
|
||||
])
|
||||
}
|
||||
|
||||
const type = Array.isArray(data) ? 'array' : typeof data
|
||||
|
||||
// Object / Array
|
||||
if (type === 'object' || type === 'array') {
|
||||
const entries = type === 'array'
|
||||
? data.map((v, i) => [i, v])
|
||||
: Object.entries(data)
|
||||
const count = entries.length
|
||||
const open = type === 'array' ? '[' : '{'
|
||||
const close = type === 'array' ? ']' : '}'
|
||||
|
||||
if (count === 0) {
|
||||
return h('div', { style: { paddingLeft: pl } }, [
|
||||
...keyFrag,
|
||||
h('span', { style: { color: C.punct } }, `${open}${close}`),
|
||||
comma
|
||||
])
|
||||
}
|
||||
|
||||
const arrowSt = {
|
||||
display: 'inline-block',
|
||||
width: '14px',
|
||||
color: C.arrow,
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
fontSize: '9px',
|
||||
verticalAlign: 'middle'
|
||||
}
|
||||
const summary = type === 'array'
|
||||
? `${count} item${count !== 1 ? 's' : ''}`
|
||||
: `${count} key${count !== 1 ? 's' : ''}`
|
||||
|
||||
if (collapsed.value) {
|
||||
return h('div', {
|
||||
style: { paddingLeft: pl, cursor: 'pointer', whiteSpace: 'nowrap' },
|
||||
onClick: toggle
|
||||
}, [
|
||||
h('span', { style: arrowSt }, '▶'),
|
||||
...keyFrag,
|
||||
h('span', { style: { color: C.punct } }, `${open} `),
|
||||
h('span', { style: { color: C.summary, fontStyle: 'italic' } }, summary),
|
||||
h('span', { style: { color: C.punct } }, ` ${close}`),
|
||||
comma
|
||||
])
|
||||
}
|
||||
|
||||
return h('div', {}, [
|
||||
// Opening line with toggle
|
||||
h('div', { style: { paddingLeft: pl, cursor: 'pointer' }, onClick: toggle }, [
|
||||
h('span', { style: arrowSt }, '▼'),
|
||||
...keyFrag,
|
||||
h('span', { style: { color: C.punct } }, open)
|
||||
]),
|
||||
// Children — each child provides its own paddingLeft via (depth+1)*INDENT
|
||||
...entries.map(([k, v], i) =>
|
||||
h(JsonNode, {
|
||||
key: String(k),
|
||||
data: v,
|
||||
keyName: type === 'array' ? null : String(k),
|
||||
depth: depth + 1,
|
||||
isLast: i === count - 1
|
||||
})
|
||||
),
|
||||
// Closing bracket at same indentation as the opening line
|
||||
h('div', { style: { paddingLeft: pl } }, [
|
||||
h('span', { style: { color: C.punct } }, close),
|
||||
comma
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
// Primitives
|
||||
let valueEl
|
||||
if (type === 'string') {
|
||||
valueEl = h('span', { style: { color: C.string } }, `"${data}"`)
|
||||
} else if (type === 'number') {
|
||||
valueEl = h('span', { style: { color: C.number } }, String(data))
|
||||
} else {
|
||||
// boolean
|
||||
valueEl = h('span', { style: { color: C.boolean } }, String(data))
|
||||
}
|
||||
|
||||
return h('div', { style: { paddingLeft: pl } }, [
|
||||
...keyFrag,
|
||||
valueEl,
|
||||
comma
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JsonViewer',
|
||||
components: { JsonNode },
|
||||
props: {
|
||||
data: { required: true }
|
||||
},
|
||||
setup (props) {
|
||||
const expandSignal = ref(0)
|
||||
const collapseSignal = ref(0)
|
||||
const expandMode = ref(null) // tracks last toolbar action for newly-mounted nodes
|
||||
provide('jeExpand', expandSignal)
|
||||
provide('jeCollapse', collapseSignal)
|
||||
provide('jeExpandMode', expandMode)
|
||||
|
||||
const copyDone = ref(false)
|
||||
|
||||
const copyJson = () => {
|
||||
const text = JSON.stringify(props.data, null, 2)
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
copyDone.value = true
|
||||
setTimeout(() => { copyDone.value = false }, 1500)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return () => h('div', {
|
||||
style: {
|
||||
position: 'relative',
|
||||
fontFamily: "'Consolas', 'Courier New', monospace",
|
||||
fontSize: '12px',
|
||||
lineHeight: '1.7',
|
||||
backgroundColor: '#fafafa',
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: '4px',
|
||||
maxHeight: '60vh',
|
||||
overflow: 'auto',
|
||||
padding: '10px 10px 10px 6px'
|
||||
}
|
||||
}, [
|
||||
// Toolbar (top-right, sticky): Expand All · Collapse All · Copy
|
||||
h('div', {
|
||||
style: {
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
float: 'right',
|
||||
zIndex: 1,
|
||||
marginBottom: '-20px',
|
||||
display: 'flex',
|
||||
gap: '4px'
|
||||
}
|
||||
}, [
|
||||
h('span', {
|
||||
onClick: () => { expandMode.value = 'expanded'; expandSignal.value++ },
|
||||
title: 'Expand all nodes',
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
fontSize: '11px',
|
||||
padding: '2px 8px',
|
||||
backgroundColor: '#e8e8e8',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '3px',
|
||||
color: '#555',
|
||||
userSelect: 'none'
|
||||
}
|
||||
}, '⊞ Expand all'),
|
||||
h('span', {
|
||||
onClick: () => { expandMode.value = 'collapsed'; collapseSignal.value++ },
|
||||
title: 'Collapse to default view (2 levels)',
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
fontSize: '11px',
|
||||
padding: '2px 8px',
|
||||
backgroundColor: '#e8e8e8',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '3px',
|
||||
color: '#555',
|
||||
userSelect: 'none'
|
||||
}
|
||||
}, '⊟ Collapse all'),
|
||||
h('span', {
|
||||
onClick: copyJson,
|
||||
title: 'Copy JSON',
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
fontSize: '11px',
|
||||
padding: '2px 8px',
|
||||
backgroundColor: copyDone.value ? '#d4edda' : '#e8e8e8',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '3px',
|
||||
color: copyDone.value ? '#155724' : '#555',
|
||||
userSelect: 'none'
|
||||
}
|
||||
}, copyDone.value ? '✓ Copied' : 'Copy')
|
||||
]),
|
||||
// Tree root
|
||||
h(JsonNode, { data: props.data, depth: 0, keyName: null, isLast: true })
|
||||
])
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -120,7 +120,7 @@ export default {
|
||||
},
|
||||
fetchExtensionResourceTypes () {
|
||||
this.resourceTypes = []
|
||||
const resourceTypesList = ['Cluster']
|
||||
const resourceTypesList = ['Cluster', 'PhysicalNetwork']
|
||||
resourceTypesList.forEach((item) => {
|
||||
this.resourceTypes.push({
|
||||
id: item,
|
||||
|
||||
@ -103,6 +103,7 @@ import { ref, reactive, toRaw, h } from 'vue'
|
||||
import { getAPI, postAPI } from '@/api'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import DetailsInput from '@/components/widgets/DetailsInput'
|
||||
import JsonViewer from '@/views/extension/JsonViewer.vue'
|
||||
|
||||
export default {
|
||||
name: 'RunCustomAction',
|
||||
@ -277,75 +278,88 @@ export default {
|
||||
} catch (_) {}
|
||||
const modalType = success ? this.$success : this.$error
|
||||
const contentElements = [h('p', `${message}`)]
|
||||
if (extensionMessage && !Array.isArray(extensionMessage) && typeof extensionMessage === 'object' && Object.keys(extensionMessage).length > 0) {
|
||||
extensionMessage = [extensionMessage]
|
||||
}
|
||||
if (Array.isArray(extensionMessage) && extensionMessage.length > 0 && printExtensionMessage) {
|
||||
contentElements.push(
|
||||
h('div', {
|
||||
style: {
|
||||
marginTop: '1em',
|
||||
maxHeight: '50vh',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto',
|
||||
backgroundColor: '#f6f6f6',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
display: 'block'
|
||||
}
|
||||
}, [
|
||||
h('table', {
|
||||
if (printExtensionMessage && extensionMessage !== null && extensionMessage !== undefined) {
|
||||
const isArray = Array.isArray(extensionMessage)
|
||||
// A "flat" array has only primitive values — safe to render as a table.
|
||||
const isFlatArray = isArray && extensionMessage.length > 0 &&
|
||||
extensionMessage.every(row =>
|
||||
typeof row === 'object' && row !== null &&
|
||||
Object.values(row).every(v => v === null || typeof v !== 'object')
|
||||
)
|
||||
if (isFlatArray) {
|
||||
contentElements.push(
|
||||
h('div', {
|
||||
style: {
|
||||
width: '100%',
|
||||
minWidth: 'max-content',
|
||||
borderCollapse: 'collapse',
|
||||
whiteSpace: 'pre-wrap'
|
||||
marginTop: '1em',
|
||||
maxHeight: '50vh',
|
||||
maxWidth: '100%',
|
||||
overflow: 'auto',
|
||||
backgroundColor: '#f6f6f6',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
display: 'block'
|
||||
}
|
||||
}, [
|
||||
h('thead', [
|
||||
h('tr', Object.keys(extensionMessage[0]).map(key =>
|
||||
h('th', {
|
||||
style: {
|
||||
padding: '8px',
|
||||
border: '1px solid #ddd',
|
||||
textAlign: 'left',
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#fafafa'
|
||||
}
|
||||
}, key)
|
||||
h('table', {
|
||||
style: {
|
||||
width: '100%',
|
||||
minWidth: 'max-content',
|
||||
borderCollapse: 'collapse',
|
||||
whiteSpace: 'pre-wrap'
|
||||
}
|
||||
}, [
|
||||
h('thead', [
|
||||
h('tr', Object.keys(extensionMessage[0]).map(key =>
|
||||
h('th', {
|
||||
style: {
|
||||
padding: '8px',
|
||||
border: '1px solid #ddd',
|
||||
textAlign: 'left',
|
||||
fontWeight: 'bold',
|
||||
backgroundColor: '#fafafa'
|
||||
}
|
||||
}, key)
|
||||
))
|
||||
]),
|
||||
h('tbody', extensionMessage.map(row =>
|
||||
h('tr', Object.values(row).map(value =>
|
||||
h('td', {
|
||||
style: {
|
||||
padding: '8px',
|
||||
border: '1px solid #ddd',
|
||||
fontFamily: 'monospace'
|
||||
}
|
||||
}, String(value))
|
||||
))
|
||||
))
|
||||
]),
|
||||
h('tbody', extensionMessage.map(row =>
|
||||
h('tr', Object.values(row).map(value =>
|
||||
h('td', {
|
||||
style: {
|
||||
padding: '8px',
|
||||
border: '1px solid #ddd',
|
||||
fontFamily: 'monospace'
|
||||
}
|
||||
}, String(value))
|
||||
))
|
||||
))
|
||||
])
|
||||
])
|
||||
])
|
||||
)
|
||||
} else if (printExtensionMessage === 'true') {
|
||||
contentElements.push(
|
||||
h('div', {
|
||||
style: {
|
||||
marginTop: '1em',
|
||||
maxHeight: '50vh',
|
||||
padding: '10px',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: '#f6f6f6',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
}
|
||||
}, String(extensionMessage))
|
||||
)
|
||||
)
|
||||
} else if (typeof extensionMessage === 'string') {
|
||||
contentElements.push(
|
||||
h('div', {
|
||||
style: {
|
||||
marginTop: '1em',
|
||||
maxHeight: '50vh',
|
||||
padding: '10px',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: '#f6f6f6',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word'
|
||||
}
|
||||
}, extensionMessage)
|
||||
)
|
||||
} else {
|
||||
// Complex object or non-flat array — render as interactive JSON tree.
|
||||
contentElements.push(
|
||||
h('div', { style: { marginTop: '1em' } }, [
|
||||
h(JsonViewer, { data: extensionMessage })
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
modalType({
|
||||
|
||||
131
ui/src/views/extension/UpdateRegisteredExtension.vue
Normal file
131
ui/src/views/extension/UpdateRegisteredExtension.vue
Normal 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>
|
||||
@ -706,6 +706,7 @@ export default {
|
||||
}
|
||||
this.loading = true
|
||||
getAPI('listExtensions', {
|
||||
type: 'Orchestrator'
|
||||
}).then(response => {
|
||||
this.extensionsList = response.listextensionsresponse.extension || []
|
||||
}).catch(error => {
|
||||
|
||||
@ -302,6 +302,7 @@ export default {
|
||||
fetchExtensionsList () {
|
||||
this.loading = true
|
||||
getAPI('listExtensions', {
|
||||
type: 'Orchestrator'
|
||||
}).then(response => {
|
||||
this.extensionsList = response.listextensionsresponse.extension || []
|
||||
}).catch(error => {
|
||||
|
||||
@ -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,53 @@ export default {
|
||||
return
|
||||
}
|
||||
this.fetchServiceProvider()
|
||||
this.fetchRegisteredExtensions()
|
||||
},
|
||||
fetchRegisteredExtensions () {
|
||||
// Load NetworkOrchestrator extensions registered to this physical network
|
||||
getAPI('listExtensions', {
|
||||
type: 'NetworkOrchestrator',
|
||||
details: 'resource'
|
||||
}).then(json => {
|
||||
const exts = (json?.listextensionsresponse?.extension) || []
|
||||
this.registeredExtensions = exts.filter(ext =>
|
||||
(ext.resources || []).some(r => r.type === 'PhysicalNetwork' && r.id === this.resource.id)
|
||||
)
|
||||
}).catch(() => {
|
||||
this.registeredExtensions = []
|
||||
})
|
||||
},
|
||||
extensionNspItem (extName) {
|
||||
// Build a ProviderItem-compatible itemNsp descriptor for extension-backed NSPs.
|
||||
// Mirrors the structure of hardcoded entries in hardcodedNsps.
|
||||
return {
|
||||
title: extName,
|
||||
details: ['name', 'state', 'id', 'physicalnetworkid', 'servicelist'],
|
||||
actions: [
|
||||
{
|
||||
api: 'updateNetworkServiceProvider',
|
||||
icon: 'play-circle-outlined',
|
||||
listView: true,
|
||||
label: 'label.enable.provider',
|
||||
confirm: 'message.confirm.enable.provider',
|
||||
show: (record) => record && record.id && record.state === 'Disabled',
|
||||
mapping: {
|
||||
state: { value: () => 'Enabled' }
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'updateNetworkServiceProvider',
|
||||
icon: 'stop-outlined',
|
||||
listView: true,
|
||||
label: 'label.disable.provider',
|
||||
confirm: 'message.confirm.disable.provider',
|
||||
show: (record) => record && record.id && record.state === 'Enabled',
|
||||
mapping: {
|
||||
state: { value: () => 'Disabled' }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
fetchServiceProvider (name) {
|
||||
this.fetchLoading = true
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
:loading="loading"
|
||||
:columns="listCols"
|
||||
:dataSource="dataSource"
|
||||
:rowKey="record => record.id || record.name || record.nvpdeviceid || record.resourceid"
|
||||
:rowKey="record => record.id || record.name || record.nvpdeviceid || record.resourceid || record.physicalnetworkid"
|
||||
:pagination="false"
|
||||
:scroll="scrollable">
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
@ -86,6 +86,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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -739,6 +739,23 @@ export default {
|
||||
isSupportedServiceObject (obj) {
|
||||
return (obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object && 'provider' in obj)
|
||||
},
|
||||
isVpcCoreProvider (providerName, serviceName) {
|
||||
if (['VpcVirtualRouter', 'Netscaler', 'BigSwitchBcf', 'ConfigDrive'].includes(providerName)) {
|
||||
return true
|
||||
}
|
||||
return serviceName === 'Connectivity' && ['NiciraNvp', 'Ovs', 'JuniperContrailVpcRouter'].includes(providerName)
|
||||
},
|
||||
isBuiltInNetworkProvider (providerName) {
|
||||
const builtInProviders = [
|
||||
'VirtualRouter', 'JuniperContrailRouter', 'JuniperContrailVpcRouter', 'JuniperSRX', 'PaloAlto',
|
||||
'F5BigIp', 'Netscaler', 'ExternalDhcpServer', 'ExternalGateWay', 'ElasticLoadBalancerVm',
|
||||
'SecurityGroupProvider', 'VpcVirtualRouter', 'None', 'NiciraNvp', 'InternalLbVm', 'CiscoVnmc',
|
||||
'Ovs', 'Opendaylight', 'BrocadeVcs', 'GloboDns', 'BigSwitchBcf', 'ConfigDrive', 'Tungsten',
|
||||
'Nsx', 'Netris', 'BaremetalDhcpProvider', 'BaremetalPxeProvider', 'BaremetalUserdataProvider',
|
||||
'StratosphereSsp'
|
||||
]
|
||||
return builtInProviders.includes(providerName)
|
||||
},
|
||||
fetchDomainData () {
|
||||
const params = {}
|
||||
params.listAll = true
|
||||
@ -854,6 +871,9 @@ export default {
|
||||
for (var j in this.supportedServices[i].provider) {
|
||||
var provider = this.supportedServices[i].provider[j]
|
||||
provider.description = provider.name
|
||||
provider.displaytext = this.isBuiltInNetworkProvider(provider.name)
|
||||
? provider.name
|
||||
: `${provider.name} (${this.$t('label.extension')})`
|
||||
provider.enabled = true
|
||||
if (provider.name === 'VpcVirtualRouter') {
|
||||
provider.enabled = false
|
||||
@ -917,11 +937,15 @@ 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
|
||||
// extension providers, which listSupportedNetworkServices() already only returns
|
||||
// for a service once the extension is confirmed to support it.
|
||||
if (provider.name === 'InternalLbVm') {
|
||||
provider.enabled = self.lbType === 'internalLb' && svc.name === 'Lb'
|
||||
} else {
|
||||
provider.enabled = self.isVpcCoreProvider(provider.name, svc.name) ||
|
||||
!self.isBuiltInNetworkProvider(provider.name)
|
||||
}
|
||||
provider.enabled = enabledProviders.includes(provider.name)
|
||||
} else { // *** non-vpc ***
|
||||
provider.enabled = !['InternalLbVm', 'VpcVirtualRouter', 'Nsx', 'Netris'].includes(provider.name)
|
||||
}
|
||||
|
||||
@ -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,23 @@ export default {
|
||||
this.zoneLoading = false
|
||||
})
|
||||
},
|
||||
isVpcCoreProvider (providerName, serviceName) {
|
||||
if (['VpcVirtualRouter', 'Netscaler', 'BigSwitchBcf', 'ConfigDrive'].includes(providerName)) {
|
||||
return true
|
||||
}
|
||||
return serviceName === 'Connectivity' && ['NiciraNvp', 'Ovs', 'JuniperContrailVpcRouter'].includes(providerName)
|
||||
},
|
||||
isBuiltInNetworkProvider (providerName) {
|
||||
const builtInProviders = [
|
||||
'VirtualRouter', 'JuniperContrailRouter', 'JuniperContrailVpcRouter', 'JuniperSRX', 'PaloAlto',
|
||||
'F5BigIp', 'Netscaler', 'ExternalDhcpServer', 'ExternalGateWay', 'ElasticLoadBalancerVm',
|
||||
'SecurityGroupProvider', 'VpcVirtualRouter', 'None', 'NiciraNvp', 'InternalLbVm', 'CiscoVnmc',
|
||||
'Ovs', 'Opendaylight', 'BrocadeVcs', 'GloboDns', 'BigSwitchBcf', 'ConfigDrive', 'Tungsten',
|
||||
'Nsx', 'Netris', 'BaremetalDhcpProvider', 'BaremetalPxeProvider', 'BaremetalUserdataProvider',
|
||||
'StratosphereSsp'
|
||||
]
|
||||
return builtInProviders.includes(providerName)
|
||||
},
|
||||
fetchSupportedServiceData () {
|
||||
var services = []
|
||||
if (this.provider === 'NSX') {
|
||||
@ -520,82 +537,54 @@ 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 isExtension = !this.isBuiltInNetworkProvider(providerName)
|
||||
const enabled = providerName === 'InternalLbVm'
|
||||
? service.name === 'Lb'
|
||||
: this.isVpcCoreProvider(providerName, service.name) || isExtension
|
||||
return {
|
||||
name: providerName,
|
||||
description: providerName,
|
||||
displaytext: isExtension ? `${providerName} (${this.$t('label.extension')})` : 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 +636,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]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user