Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c0ec554
Add OVN provider lifecycle skeleton
msinhore Apr 22, 2026
86ba819
OVN plugin: fix SSL cert enforcement and minor cleanups
msinhore Apr 22, 2026
a726815
OVN plugin: add real Northbound client backed by ODL OVSDB library
msinhore Apr 25, 2026
839af0e
OVN plugin: health-check Northbound endpoint at addOvnProvider
msinhore Apr 25, 2026
bd8ce97
OVN plugin: create logical switches and persist system VM bootstrap
msinhore Apr 27, 2026
818cf6d
OVN plugin: expose provider in UI and configuration
msinhore Apr 27, 2026
2e8c8a6
OVN plugin: bind VM NICs to logical switches via OVSDB LSPs
msinhore Apr 28, 2026
5d46852
OVN plugin: serve DHCPv4 from OVN itself via DHCP_Options
msinhore Apr 28, 2026
7aea0b5
OVN plugin: provision per-network L3 router with source NAT
msinhore Apr 28, 2026
661f659
OVN plugin: implement StaticNat via dnat_and_snat with Gateway_Chassi…
msinhore Apr 28, 2026
21619f3
OVN plugin: enforce ingress firewall rules via OVN ACL on the public LS
msinhore Apr 28, 2026
1f7c306
OVN plugin: detach NAT rows from Logical_Router.nat before deleting them
msinhore Apr 28, 2026
ea3ecc4
OVN plugin: add no-router network offerings
msinhore Apr 28, 2026
6e7c612
OVN plugin: implement PortForwarding and NetworkACL
msinhore Apr 28, 2026
6af5297
OVN plugin: announce SourceNat IPs via gARP and stop NAT-based PortFo…
msinhore Apr 28, 2026
ed63061
OVN plugin: implement PortForwarding via Load_Balancer
msinhore Apr 28, 2026
d762a14
OVN plugin: clean up per-IP artifacts on release and prune stale gate…
msinhore Apr 28, 2026
b5fb23f
OVN plugin: implement Load Balancer (L4) with health checks
msinhore Apr 28, 2026
7e31d3c
OVN plugin: reject LB rules placed on the network's SourceNat IP
msinhore Apr 29, 2026
e4cb8ac
OVN plugin: tighten Load_Balancer cleanup and protocol gate
msinhore Apr 29, 2026
43a9ea2
OVN plugin: introduce VPC-aware naming helpers (no functional change)
msinhore Apr 29, 2026
1eb672c
OVN plugin: implementVpc / shutdownVpc / updateVpcSourceNatIp
msinhore Apr 29, 2026
37cf8c6
OVN plugin: scope addStaticRoute idempotency by router
msinhore Apr 29, 2026
30604ae
OVN plugin: provision VPC tiers as LRPs on the shared VPC LR
msinhore Apr 29, 2026
b26e10c
OVN plugin: route StaticNat / PF / Firewall through VPC LR when appli…
msinhore Apr 29, 2026
c9758ae
OVN plugin: tag VPC NetworkACLs and scope ACL sweep to the target LS
msinhore Apr 29, 2026
801e5eb
OVN plugin: tag Public LB rows with VPC and scheme metadata
msinhore Apr 29, 2026
2c73d03
OVN plugin: support Internal Load Balancer scheme natively on the VPC LR
msinhore Apr 29, 2026
051b0a4
OVN plugin: complete the auto-seeded OVN offerings for fresh installs
msinhore Apr 29, 2026
b61ed36
OVN plugin: allow VM-initiated reply traffic past the public-IP firewall
msinhore Apr 29, 2026
991cdec
OVN plugin: fix NPE in addStaticRoute when LR already has routes
msinhore Apr 29, 2026
91a9caa
OVN plugin: drop unsolicited ICMP echo on public IPs with firewall rules
msinhore Apr 29, 2026
332a5aa
ui: surface OVN as a guest isolation method in the Add Zone wizard
msinhore Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/main/java/com/cloud/network/Network.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ public static class Provider {

public static final Provider Nsx = new Provider("Nsx", false);
public static final Provider Netris = new Provider("Netris", false);
public static final Provider Ovn = new Provider("Ovn", false);

private final String name;
private final boolean isExternal;
Expand Down
3 changes: 2 additions & 1 deletion api/src/main/java/com/cloud/network/Networks.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ public <T> URI toUri(T value) {
OpenDaylight("opendaylight", String.class),
TUNGSTEN("tf", String.class),
NSX("nsx", String.class),
Netris("netris", String.class);
Netris("netris", String.class),
OVN("ovn", String.class);

private final String scheme;
private final Class<?> type;
Expand Down
33 changes: 33 additions & 0 deletions api/src/main/java/com/cloud/network/ovn/OvnProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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.ovn;

import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;

public interface OvnProvider extends InternalIdentity, Identity {
long getZoneId();
Long getHostId();
String getName();
String getNbConnection();
String getSbConnection();
String getCaCertPath();
String getClientCertPath();
String getClientPrivateKeyPath();
String getExternalBridge();
String getLocalnetName();
}
34 changes: 34 additions & 0 deletions api/src/main/java/com/cloud/network/ovn/OvnService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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.ovn;

/**
* Service boundary for CloudStack's native OVN integration.
*/
public interface OvnService {
String getLogicalSwitchName(long networkId);
String getLogicalRouterName(long vpcId);
String getLogicalSwitchPortName(long nicId);
boolean isValidConnectionString(String connection);

/**
* Opens a transient connection to the OVN Northbound endpoint described by the arguments,
* runs an OVSDB echo and confirms the OVN_Northbound database is advertised. Throws a
* {@link com.cloud.utils.exception.CloudRuntimeException} on any failure, leaving no resources behind.
*/
void verifyNbConnection(String nbConnection, String caCertPath, String clientCertPath, String clientPrivateKeyPath);
}
1 change: 1 addition & 0 deletions api/src/main/java/com/cloud/network/vpc/VpcOffering.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public enum State {
public static final String DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME = "VPC offering with NSX - Route Mode";
public static final String DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME = "VPC offering with Netris - Route Mode";
public static final String DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME = "VPC offering with Netris - NAT Mode";
public static final String DEFAULT_VPC_NAT_OVN_OFFERING_NAME = "VPC offering with OVN - NAT Mode";

/**
*
Expand Down
2 changes: 2 additions & 0 deletions api/src/main/java/com/cloud/offering/NetworkOffering.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ enum RoutingMode {
public static final String DEFAULT_ROUTED_NSX_OFFERING_FOR_VPC = "DefaultRoutedNSXNetworkOfferingForVpc";
public static final String DEFAULT_ROUTED_NETRIS_OFFERING_FOR_VPC = "DefaultRoutedNetrisNetworkOfferingForVpc";
public static final String DEFAULT_NAT_NETRIS_OFFERING_FOR_VPC = "DefaultNATNetrisNetworkOfferingForVpc";
public static final String DEFAULT_NAT_OVN_OFFERING = "DefaultNATOVNNetworkOffering";
public static final String DEFAULT_NAT_OVN_OFFERING_FOR_VPC = "DefaultNATOVNNetworkOfferingForVpc";
public static final String DEFAULT_NAT_NSX_OFFERING = "DefaultNATNSXNetworkOffering";
public static final String DEFAULT_ROUTED_NSX_OFFERING = "DefaultRoutedNSXNetworkOffering";
public final static String QuickCloudNoServices = "QuickCloudNoServices";
Expand Down
8 changes: 8 additions & 0 deletions api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,13 @@ public class ApiConstants {

public static final String NSX_PROVIDER_PORT = "nsxproviderport";
public static final String NSX_CONTROLLER_ID = "nsxcontrollerid";
public static final String OVN_NB_CONNECTION = "ovnnbconnection";
public static final String OVN_SB_CONNECTION = "ovnsbconnection";
public static final String OVN_CA_CERT_PATH = "ovncacertpath";
public static final String OVN_CLIENT_CERT_PATH = "ovnclientcertpath";
public static final String OVN_CLIENT_PRIVATE_KEY_PATH = "ovnclientprivatekeypath";
public static final String OVN_EXTERNAL_BRIDGE = "ovnexternalbridge";
public static final String OVN_LOCALNET_NAME = "ovnlocalnetname";
public static final String S3_ACCESS_KEY = "accesskey";
public static final String SECRET_KEY = "secretkey";
public static final String S3_END_POINT = "endpoint";
Expand Down Expand Up @@ -1307,6 +1314,7 @@ public class ApiConstants {
public static final String HAS_RULES = "hasrules";
public static final String NSX_DETAIL_KEY = "forNsx";
public static final String NETRIS_DETAIL_KEY = "forNetris";
public static final String OVN_DETAIL_KEY = "forOvn";
public static final String NETRIS_TAG = "netristag";
public static final String NETRIS_VXLAN_ID = "netrisvxlanid";
public static final String NETRIS_URL = "netrisurl";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public class CreatePhysicalNetworkCmd extends BaseAsyncCreateCmd {
@Parameter(name = ApiConstants.ISOLATION_METHODS,
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "The isolation method for the physical Network[VLAN/VXLAN/GRE/STT/BCF_SEGMENT/SSP/ODL/L3VPN/VCS/NSX/NETRIS]")
description = "The isolation method for the physical Network[VLAN/VXLAN/GRE/STT/BCF_SEGMENT/SSP/ODL/L3VPN/VCS/NSX/NETRIS/OVN]")
private List<String> isolationMethods;

@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the physical Network")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNsxWithoutLb;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisNatted;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisRouted;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isOvnProvider;

public abstract class NetworkOfferingBaseCmd extends BaseCmd {

Expand Down Expand Up @@ -249,7 +250,7 @@ public Long getServiceOfferingId() {
}

public boolean isExternalNetworkProvider() {
return Arrays.asList("NSX", "Netris").stream()
return Arrays.asList("NSX", "Netris", "OVN").stream()
.anyMatch(s -> provider != null && s.equalsIgnoreCase(provider));
}

Expand All @@ -271,16 +272,18 @@ public List<String> getSupportedServices() {
} else {
List<String> services = new ArrayList<>(List.of(
Dhcp.getName(),
Dns.getName(),
UserData.getName()
Dns.getName()
));
if (!isOvnProvider(getProvider())) {
services.add(UserData.getName());
}
if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) {
services.addAll(Arrays.asList(
StaticNat.getName(),
SourceNat.getName(),
PortForwarding.getName()));
}
if (getNsxSupportsLbService() || (provider != null && isNetrisNatted(getProvider(), getNetworkMode()))) {
if (getNsxSupportsLbService() || (provider != null && (isNetrisNatted(getProvider(), getNetworkMode()) || isOvnProvider(getProvider())))) {
services.add(Lb.getName());
}
if (Boolean.TRUE.equals(forVpc)) {
Expand Down Expand Up @@ -374,7 +377,7 @@ private void getServiceProviderMapForExternalProvider(Map<String, List<String>>
String routerProvider = Boolean.TRUE.equals(getForVpc()) ? VirtualRouterProvider.Type.VPCVirtualRouter.name() :
VirtualRouterProvider.Type.VirtualRouter.name();
List<String> unsupportedServices = new ArrayList<>(List.of("Vpn", "Gateway", "SecurityGroup", "Connectivity", "BaremetalPxeService"));
List<String> routerSupported = List.of("Dhcp", "Dns", "UserData");
List<String> routerSupported = isOvnProvider(provider) ? List.of() : List.of("Dhcp", "Dns", "UserData");
List<String> allServices = Network.Service.listAllServices().stream().map(Network.Service::getName).collect(Collectors.toList());
if (routerProvider.equals(VirtualRouterProvider.Type.VPCVirtualRouter.name())) {
unsupportedServices.add("Firewall");
Expand All @@ -386,6 +389,9 @@ private void getServiceProviderMapForExternalProvider(Map<String, List<String>>
continue;
if (routerSupported.contains(service))
serviceProviderMap.put(service, List.of(routerProvider));
else if (isOvnProvider(provider) && (Dhcp.getName().equalsIgnoreCase(service) || Dns.getName().equalsIgnoreCase(service))) {
serviceProviderMap.put(service, List.of(provider));
}
else if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode()) || NetworkACL.getName().equalsIgnoreCase(service)) {
serviceProviderMap.put(service, List.of(provider));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisNatted;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisRouted;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNsxWithoutLb;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isOvnProvider;

@APICommand(name = "createVPCOffering", description = "Creates VPC offering", responseObject = VpcOfferingResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
Expand Down Expand Up @@ -179,7 +180,7 @@ public String getDisplayText() {
}

public boolean isExternalNetworkProvider() {
return Arrays.asList("NSX", "Netris").stream()
return Arrays.asList("NSX", "Netris", "OVN").stream()
.anyMatch(s -> provider != null && s.equalsIgnoreCase(provider));
}

Expand All @@ -189,9 +190,11 @@ public List<String> getSupportedServices() {
supportedServices = new ArrayList<>(List.of(
Dhcp.getName(),
Dns.getName(),
NetworkACL.getName(),
UserData.getName()
NetworkACL.getName()
));
if (!isOvnProvider(getProvider())) {
supportedServices.add(UserData.getName());
}
if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) {
supportedServices.addAll(Arrays.asList(
StaticNat.getName(),
Expand All @@ -201,7 +204,7 @@ public List<String> getSupportedServices() {
if (NetworkOffering.NetworkMode.ROUTED.name().equalsIgnoreCase(getNetworkMode())) {
supportedServices.add(Gateway.getName());
}
if (getNsxSupportsLbService() || isNetrisNatted(getProvider(), getNetworkMode())) {
if (getNsxSupportsLbService() || isNetrisNatted(getProvider(), getNetworkMode()) || isOvnProvider(getProvider())) {
supportedServices.add(Lb.getName());
}
}
Expand Down Expand Up @@ -252,13 +255,16 @@ private void getServiceProviderMapForExternalProvider(Map<String, List<String>>
if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) {
unsupportedServices.add("Gateway");
}
List<String> routerSupported = List.of("Dhcp", "Dns", "UserData");
List<String> routerSupported = isOvnProvider(provider) ? List.of() : List.of("Dhcp", "Dns", "UserData");
List<String> allServices = Network.Service.listAllServices().stream().map(Network.Service::getName).collect(Collectors.toList());
for (String service : allServices) {
if (unsupportedServices.contains(service))
continue;
if (routerSupported.contains(service))
serviceProviderMap.put(service, List.of(VirtualRouterProvider.Type.VPCVirtualRouter.name()));
else if (isOvnProvider(provider) && (Dhcp.getName().equalsIgnoreCase(service) || Dns.getName().equalsIgnoreCase(service))) {
serviceProviderMap.put(service, List.of(provider));
}
else if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode()) ||
Stream.of(NetworkACL.getName(), Gateway.getName()).anyMatch(s -> s.equalsIgnoreCase(service))) {
serviceProviderMap.put(service, List.of(provider));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ public static boolean isNsxWithoutLb(String provider, boolean nsxSupportsLbServi
public static boolean isNetrisRouted(String provider, String networkMode) {
return "Netris".equalsIgnoreCase(provider) && NetworkOffering.NetworkMode.ROUTED.name().equalsIgnoreCase(networkMode);
}

public static boolean isOvnProvider(String provider) {
return "Ovn".equalsIgnoreCase(provider);
}
}
5 changes: 5 additions & 0 deletions client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,11 @@
<artifactId>cloud-plugin-network-opendaylight</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-network-ovn</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-network-vcs</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public static String getEncodedString(String certificate) {
return Base64.getEncoder().encodeToString(certificate.replace("\n", KeyStoreUtils.CERT_NEWLINE_ENCODER).replace(" ", KeyStoreUtils.CERT_SPACE_ENCODER).getBytes(StandardCharsets.UTF_8));
}

static void appendCertificateDetails(StringBuilder buf, Certificate certificate) {
public static void appendCertificateDetails(StringBuilder buf, Certificate certificate) {
try {
buf.append(" certificate=").append(getEncodedString(CertUtils.x509CertificateToPem(certificate.getClientCertificate())));
buf.append(" cacertificate=").append(getEncodedString(CertUtils.x509CertificatesToPem(certificate.getCaCertificates())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ public interface NetworkOrchestrationService {

ConfigKey<Boolean> NETRIS_ENABLED = new ConfigKey<>(Boolean.class, "netris.plugin.enable", "Advanced", "false",
"Indicates whether to enable the Netris plugin", false, ConfigKey.Scope.Zone, null);

ConfigKey<Boolean> OVN_ENABLED = new ConfigKey<>(Boolean.class, "ovn.plugin.enable", "Advanced", "false",
"Indicates whether to enable the OVN plugin", false, ConfigKey.Scope.Zone, null);
ConfigKey<Integer> NETWORK_LB_HAPROXY_MAX_CONN = new ConfigKey<>(
"Network",
Integer.class,
Expand Down
Loading