From 2cfc768b13ebaec6147f3d85c10fb5eaae78c3f6 Mon Sep 17 00:00:00 2001 From: Chinmay Soni Date: Wed, 15 Jul 2026 12:15:41 +0000 Subject: [PATCH 1/4] improvement(offering): make network mode mandatory when creating vpc and network offerings (#13607) --- .../api/command/admin/network/NetworkOfferingBaseCmd.java | 7 ++----- .../api/command/admin/vpc/CreateVPCOfferingCmd.java | 5 +---- ui/src/views/offering/AddNetworkOffering.vue | 2 ++ ui/src/views/offering/AddVpcOffering.vue | 2 ++ 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java index 1c832b7217ef..9e5aed091913 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java @@ -153,11 +153,8 @@ public abstract class NetworkOfferingBaseCmd extends BaseCmd { since = "4.20.0") private Boolean nsxSupportsInternalLbService; - @Parameter(name = ApiConstants.NETWORK_MODE, - type = CommandType.STRING, - description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED", - since = "4.20.0") - private String networkMode; + @Parameter(name = ApiConstants.NETWORK_MODE, type = CommandType.STRING, required = true, description = "the network mode for the network offering") + private String networkMode; @Parameter(name = ApiConstants.FOR_TUNGSTEN, type = CommandType.BOOLEAN, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java index 2b934a60da7a..e75f8b258d68 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java @@ -144,10 +144,7 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { since = "4.16") private Boolean enable; - @Parameter(name = ApiConstants.NETWORK_MODE, - type = CommandType.STRING, - description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED", - since = "4.20.0") + @Parameter(name = ApiConstants.NETWORK_MODE, type = CommandType.STRING, required = true, description = "the network mode for the VPC offering") private String networkMode; @Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0", diff --git a/ui/src/views/offering/AddNetworkOffering.vue b/ui/src/views/offering/AddNetworkOffering.vue index 995b81ce68c6..b36fd82b6677 100644 --- a/ui/src/views/offering/AddNetworkOffering.vue +++ b/ui/src/views/offering/AddNetworkOffering.vue @@ -711,6 +711,8 @@ export default { }) this.rules = reactive({ name: [{ required: true, message: this.$t('message.error.name') }], + // ADD THIS LINE: + networkmode: [{ required: true, message: this.$t('message.error.select') }], networkrate: [{ type: 'number', validator: this.validateNumber }], serviceofferingid: [{ required: true, message: this.$t('message.error.select') }], domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }], diff --git a/ui/src/views/offering/AddVpcOffering.vue b/ui/src/views/offering/AddVpcOffering.vue index 780da6638f8e..470b56920628 100644 --- a/ui/src/views/offering/AddVpcOffering.vue +++ b/ui/src/views/offering/AddVpcOffering.vue @@ -366,6 +366,8 @@ export default { }) this.rules = reactive({ name: [{ required: true, message: this.$t('message.error.name') }], + // ADD THIS LINE: + networkmode: [{ required: true, message: this.$t('message.error.select') }], domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }], zoneid: [{ type: 'array', From c1e4dc5182215c9dd0063e7ae85b800a3fd3bafc Mon Sep 17 00:00:00 2001 From: Chinmay Soni Date: Fri, 24 Jul 2026 10:52:32 +0000 Subject: [PATCH 2/4] Restore API parameter descriptions and add mandatory networkMode validation tests --- .../network/CreateNetworkOfferingCmd.java | 121 ++++++++++++++++++ .../admin/vpc/CreateVPCOfferingCmd.java | 9 ++ 2 files changed, 130 insertions(+) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java index 5c39060f9fa3..12a4d7d43790 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java @@ -42,6 +42,127 @@ public class CreateNetworkOfferingCmd extends NetworkOfferingBaseCmd { @Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, required = true, description = "Guest type of the network offering: Shared or Isolated") private String guestIptype; +<<<<<<< Updated upstream +======= + @Parameter(name = ApiConstants.INTERNET_PROTOCOL, + type = CommandType.STRING, + description = "The internet protocol of network offering. Options are IPv4 and dualstack. Default is IPv4. dualstack will create a network offering that supports both IPv4 and IPv6", + since = "4.17.0") + private String internetProtocol; + + @Parameter(name = ApiConstants.SUPPORTED_SERVICES, + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = "Services supported by the network offering") + private List supportedServices; + + @Parameter(name = ApiConstants.SERVICE_PROVIDER_LIST, + type = CommandType.MAP, + description = "Provider to service mapping. If not specified, the provider for the service will be mapped to the default provider on the physical network") + private Map serviceProviderList; + + @Parameter(name = ApiConstants.SERVICE_CAPABILITY_LIST, type = CommandType.MAP, description = "Desired service capabilities as part of network offering") + private Map serviceCapabilitystList; + + @Parameter(name = ApiConstants.SPECIFY_IP_RANGES, + type = CommandType.BOOLEAN, + description = "True if network offering supports specifying ip ranges; defaulted to false if not specified") + private Boolean specifyIpRanges; + + @Parameter(name = ApiConstants.IS_PERSISTENT, + type = CommandType.BOOLEAN, + description = "True if network offering supports persistent networks; defaulted to false if not specified") + private Boolean isPersistent; + + @Parameter(name = ApiConstants.FOR_VPC, + type = CommandType.BOOLEAN, + description = "True if network offering is meant to be used for VPC, false otherwise.") + private Boolean forVpc; + + @Parameter(name = ApiConstants.FOR_NSX, + type = CommandType.BOOLEAN, + description = "true if network offering is meant to be used for NSX, false otherwise.", + since = "4.20.0") + private Boolean forNsx; + + @Parameter(name = ApiConstants.NSX_SUPPORT_LB, + type = CommandType.BOOLEAN, + description = "True if network offering for NSX network offering supports Load balancer service.", + since = "4.20.0") + private Boolean nsxSupportsLbService; + + @Parameter(name = ApiConstants.NSX_SUPPORTS_INTERNAL_LB, + type = CommandType.BOOLEAN, + description = "True if network offering for NSX network offering supports Internal Load balancer service.", + since = "4.20.0") + private Boolean nsxSupportsInternalLbService; + + @Parameter( + name = ApiConstants.NETWORK_MODE, + type = CommandType.STRING, + required = true, + description = "the network mode of the network offering, possible values are NATTED and ROUTED" + ) + private String networkMode; + + @Parameter(name = ApiConstants.FOR_TUNGSTEN, + type = CommandType.BOOLEAN, + description = "True if network offering is meant to be used for Tungsten-Fabric, false otherwise.") + private Boolean forTungsten; + + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.2.0", description = "Network offering details in key/value pairs." + + " Supported keys are internallbprovider/publiclbprovider with service provider as a value, and" + + " promiscuousmode/macaddresschanges/forgedtransmits with true/false as value to accept/reject the security settings if available for a nic/portgroup") + protected Map details; + + @Parameter(name = ApiConstants.EGRESS_DEFAULT_POLICY, + type = CommandType.BOOLEAN, + description = "True if guest network default egress policy is allow; false if default egress policy is deny") + private Boolean egressDefaultPolicy; + + @Parameter(name = ApiConstants.KEEPALIVE_ENABLED, + type = CommandType.BOOLEAN, + required = false, + description = "If true keepalive will be turned on in the loadbalancer. At the time of writing this has only an effect on haproxy; the mode http and httpclose options are unset in the haproxy conf file.") + private Boolean keepAliveEnabled; + + @Parameter(name = ApiConstants.MAX_CONNECTIONS, + type = CommandType.INTEGER, + description = "Maximum number of concurrent connections supported by the Network offering") + private Integer maxConnections; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = DomainResponse.class, + description = "The ID of the containing domain(s), null for public offerings") + private List domainIds; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = ZoneResponse.class, + description = "The ID of the containing zone(s), null for public offerings", + since = "4.13") + private List zoneIds; + + @Parameter(name = ApiConstants.ENABLE, + type = CommandType.BOOLEAN, + description = "Set to true if the offering is to be enabled during creation. Default is false", + since = "4.16") + private Boolean enable; + + @Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0", + description = "true if network offering supports choosing AS number") + private Boolean specifyAsNumber; + + @Parameter(name = ApiConstants.ROUTING_MODE, + type = CommandType.STRING, + since = "4.20.0", + description = "the routing mode for the network offering. Supported types are: Static or Dynamic.") + private String routingMode; + +>>>>>>> Stashed changes ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java index e75f8b258d68..f3d747390a1e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java @@ -144,7 +144,16 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { since = "4.16") private Boolean enable; +<<<<<<< Updated upstream @Parameter(name = ApiConstants.NETWORK_MODE, type = CommandType.STRING, required = true, description = "the network mode for the VPC offering") +======= + @Parameter( + name = ApiConstants.NETWORK_MODE, + type = CommandType.STRING, + required = true, + description = "the network mode of the vpc, possible values are NATTED and ROUTED" + ) +>>>>>>> Stashed changes private String networkMode; @Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0", From a5783aeb2a882b8584415e082781f4592c567473 Mon Sep 17 00:00:00 2001 From: Chinmay Soni Date: Fri, 24 Jul 2026 10:58:54 +0000 Subject: [PATCH 3/4] Restore API parameter descriptions and add mandatory networkMode validation tests --- .../api/command/admin/network/CreateNetworkOfferingCmd.java | 4 +--- .../api/command/admin/vpc/CreateVPCOfferingCmd.java | 6 +----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java index 12a4d7d43790..e8ddadeff2ae 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java @@ -42,8 +42,6 @@ public class CreateNetworkOfferingCmd extends NetworkOfferingBaseCmd { @Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, required = true, description = "Guest type of the network offering: Shared or Isolated") private String guestIptype; -<<<<<<< Updated upstream -======= @Parameter(name = ApiConstants.INTERNET_PROTOCOL, type = CommandType.STRING, description = "The internet protocol of network offering. Options are IPv4 and dualstack. Default is IPv4. dualstack will create a network offering that supports both IPv4 and IPv6", @@ -162,7 +160,7 @@ public class CreateNetworkOfferingCmd extends NetworkOfferingBaseCmd { description = "the routing mode for the network offering. Supported types are: Static or Dynamic.") private String routingMode; ->>>>>>> Stashed changes + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java index f3d747390a1e..c4d55cf77950 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java @@ -144,16 +144,12 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { since = "4.16") private Boolean enable; -<<<<<<< Updated upstream - @Parameter(name = ApiConstants.NETWORK_MODE, type = CommandType.STRING, required = true, description = "the network mode for the VPC offering") -======= - @Parameter( + @Parameter( name = ApiConstants.NETWORK_MODE, type = CommandType.STRING, required = true, description = "the network mode of the vpc, possible values are NATTED and ROUTED" ) ->>>>>>> Stashed changes private String networkMode; @Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0", From 6179cb75c7f7e6fb7fbb31075562322af6d1ecda Mon Sep 17 00:00:00 2001 From: Chinmay Soni Date: Mon, 27 Jul 2026 14:26:23 +0000 Subject: [PATCH 4/4] refactor(offering): make network and routing modes optional at API boundary with defensive server-side defaults --- .../command/admin/network/CreateNetworkOfferingCmd.java | 7 ++++++- .../api/command/admin/vpc/CreateVPCOfferingCmd.java | 7 +++---- .../com/cloud/configuration/ConfigurationManagerImpl.java | 7 +++++-- .../main/java/com/cloud/network/vpc/VpcManagerImpl.java | 7 +++++-- ui/src/views/offering/AddNetworkOffering.vue | 1 - ui/src/views/offering/AddVpcOffering.vue | 1 - 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java index e8ddadeff2ae..d7e605bd38fa 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java @@ -16,12 +16,18 @@ // under the License. package org.apache.cloudstack.api.command.admin.network; + +import java.util.List; +import java.util.Map; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.NetworkOfferingResponse; +import org.apache.cloudstack.api.response.ZoneResponse; + import com.cloud.offering.NetworkOffering; @@ -98,7 +104,6 @@ public class CreateNetworkOfferingCmd extends NetworkOfferingBaseCmd { @Parameter( name = ApiConstants.NETWORK_MODE, type = CommandType.STRING, - required = true, description = "the network mode of the network offering, possible values are NATTED and ROUTED" ) private String networkMode; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java index c4d55cf77950..94809426e705 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java @@ -145,10 +145,9 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { private Boolean enable; @Parameter( - name = ApiConstants.NETWORK_MODE, - type = CommandType.STRING, - required = true, - description = "the network mode of the vpc, possible values are NATTED and ROUTED" + name = ApiConstants.NETWORK_MODE, + type = CommandType.STRING, + description = "the network mode of the vpc, possible values are NATTED and ROUTED" ) private String networkMode; diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 164193beeda0..48ad3e223c60 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -7181,8 +7181,8 @@ public NetworkOffering createNetworkOffering(final NetworkOfferingBaseCmd cmd) { throw new InvalidParameterValueException("Network Offering cannot be for multiple providers - Tungsten-Fabric, NSX and Netris"); } - NetworkOffering.NetworkMode networkMode = null; - if (networkModeStr != null) { + NetworkOffering.NetworkMode networkMode = NetworkOffering.NetworkMode.NATTED; // Defensive fallback + if (StringUtils.isNotEmpty(networkModeStr)) { if (!EnumUtils.isValidEnum(NetworkOffering.NetworkMode.class, networkModeStr)) { throw new InvalidParameterValueException("Invalid mode passed. Valid values: " + Arrays.toString(NetworkOffering.NetworkMode.values())); } @@ -7248,6 +7248,9 @@ public NetworkOffering createNetworkOffering(final NetworkOfferingBaseCmd cmd) { _networkSvc.validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(serviceOfferingId); } + if (StringUtils.isEmpty(routingModeString)) { + routingModeString = NetworkOffering.RoutingMode.Static.toString(); // Defensive fallback + } NetworkOffering.RoutingMode routingMode = verifyRoutingMode(routingModeString); // configure service provider map diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index bcf2c6176efe..dc931f3f0354 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -572,8 +572,8 @@ public VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { final String networkModeStr = cmd.getNetworkMode(); final boolean enable = cmd.getEnable(); - NetworkOffering.NetworkMode networkMode = null; - if (networkModeStr != null) { + NetworkOffering.NetworkMode networkMode = NetworkOffering.NetworkMode.NATTED; + if (StringUtils.isNotEmpty(networkModeStr)) { if (!EnumUtils.isValidEnum(NetworkOffering.NetworkMode.class, networkModeStr)) { throw new InvalidParameterValueException("Invalid mode passed. Valid values: " + Arrays.toString(NetworkOffering.NetworkMode.values())); } @@ -616,6 +616,9 @@ public VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { _ntwkSvc.validateIfServiceOfferingIsActiveAndSystemVmTypeIsDomainRouter(serviceOfferingId); } + if (StringUtils.isEmpty(routingModeString)) { + routingModeString = NetworkOffering.RoutingMode.Static.toString(); // Defensive fallback for standard routing + } NetworkOffering.RoutingMode routingMode = ConfigurationManagerImpl.verifyRoutingMode(routingModeString); if (specifyAsNumber && !forNsx) { diff --git a/ui/src/views/offering/AddNetworkOffering.vue b/ui/src/views/offering/AddNetworkOffering.vue index b36fd82b6677..11e9903485e2 100644 --- a/ui/src/views/offering/AddNetworkOffering.vue +++ b/ui/src/views/offering/AddNetworkOffering.vue @@ -711,7 +711,6 @@ export default { }) this.rules = reactive({ name: [{ required: true, message: this.$t('message.error.name') }], - // ADD THIS LINE: networkmode: [{ required: true, message: this.$t('message.error.select') }], networkrate: [{ type: 'number', validator: this.validateNumber }], serviceofferingid: [{ required: true, message: this.$t('message.error.select') }], diff --git a/ui/src/views/offering/AddVpcOffering.vue b/ui/src/views/offering/AddVpcOffering.vue index 470b56920628..6c7391169e11 100644 --- a/ui/src/views/offering/AddVpcOffering.vue +++ b/ui/src/views/offering/AddVpcOffering.vue @@ -366,7 +366,6 @@ export default { }) this.rules = reactive({ name: [{ required: true, message: this.$t('message.error.name') }], - // ADD THIS LINE: networkmode: [{ required: true, message: this.$t('message.error.select') }], domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }], zoneid: [{