This is an automated email from the ASF dual-hosted git repository.

dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 93239e09f19 Add conserve mode for VPC offerings (#12487)
93239e09f19 is described below

commit 93239e09f198964f3b3a3155c124dac7fa3b9d89
Author: Nicolas Vazquez <[email protected]>
AuthorDate: Mon Mar 16 05:39:42 2026 -0300

    Add conserve mode for VPC offerings (#12487)
---
 .github/workflows/ci.yml                           |   1 +
 .../java/com/cloud/network/NetworkService.java     |   2 +
 .../network/lb/LoadBalancingRulesService.java      |   2 +-
 .../java/com/cloud/network/vpc/VpcOffering.java    |   2 +
 .../cloud/network/vpc/VpcProvisioningService.java  |   2 +-
 .../org/apache/cloudstack/api/ApiConstants.java    |   1 +
 .../command/admin/vpc/CreateVPCOfferingCmd.java    |  10 +
 .../user/firewall/CreatePortForwardingRuleCmd.java |   9 +-
 .../loadbalancer/AssignToLoadBalancerRuleCmd.java  |  28 +-
 .../api/response/FirewallRuleResponse.java         |   8 +
 .../api/response/VpcOfferingResponse.java          |  12 +
 .../cloudstack/api/response/VpcResponse.java       |   8 +
 .../java/com/cloud/network/IpAddressManager.java   |   2 +
 .../network/lb/LoadBalancingRulesManager.java      |   2 +-
 .../java/com/cloud/network/vpc/VpcManager.java     |   5 +
 .../java/com/cloud/network/vpc/VpcOfferingVO.java  |  12 +
 .../resources/META-INF/db/schema-42210to42300.sql  |   3 +
 .../META-INF/db/views/cloud.vpc_offering_view.sql  |   1 +
 ...ernetesClusterResourceModifierActionWorker.java |   2 +-
 .../cloud/network/lb/LoadBalanceRuleHandler.java   |   2 +-
 .../contrail/management/ContrailManagerImpl.java   |   2 +-
 .../main/java/com/cloud/api/ApiResponseHelper.java |   3 +-
 .../api/query/dao/VpcOfferingJoinDaoImpl.java      |   1 +
 .../com/cloud/api/query/vo/VpcOfferingJoinVO.java  |   8 +
 .../com/cloud/network/IpAddressManagerImpl.java    |  41 ++-
 .../java/com/cloud/network/NetworkServiceImpl.java |   5 +
 .../com/cloud/network/as/AutoScaleManagerImpl.java |   2 +-
 .../network/firewall/FirewallManagerImpl.java      |  27 +-
 .../network/lb/LoadBalancingRulesManagerImpl.java  | 110 ++++++--
 .../java/com/cloud/network/vpc/VpcManagerImpl.java |  38 ++-
 .../cloud/network/as/AutoScaleManagerImplTest.java |   4 +-
 .../network/firewall/FirewallManagerTest.java      |  98 +++++--
 .../cloud/network/lb/AssignLoadBalancerTest.java   |  18 +-
 .../lb/LoadBalancingRulesManagerImplTest.java      |  52 ++++
 .../com/cloud/network/vpc/VpcManagerImplTest.java  |  23 ++
 .../java/com/cloud/vpc/MockNetworkManagerImpl.java |   5 +
 .../integration/smoke/test_domain_vpc_offerings.py | 167 ++++++++++-
 test/integration/smoke/test_vpc_conserve_mode.py   | 314 +++++++++++++++++++++
 tools/marvin/marvin/lib/base.py                    |   2 +
 ui/src/config/section/offering.js                  |   2 +-
 ui/src/views/network/LoadBalancing.vue             |  36 ++-
 ui/src/views/network/PortForwarding.vue            |  42 ++-
 ui/src/views/network/PublicIpResource.vue          |  28 +-
 ui/src/views/offering/AddVpcOffering.vue           |  13 +-
 44 files changed, 1040 insertions(+), 115 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4edd448067a..6957d3f5446 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -146,6 +146,7 @@ jobs:
                   smoke/test_vm_snapshot_kvm
                   smoke/test_vm_snapshots
                   smoke/test_volumes
+                  smoke/test_vpc_conserve_mode
                   smoke/test_vpc_ipv6
                   smoke/test_vpc_redundant
                   smoke/test_vpc_router_nics
diff --git a/api/src/main/java/com/cloud/network/NetworkService.java 
b/api/src/main/java/com/cloud/network/NetworkService.java
index 742206c7e3b..53692f932a4 100644
--- a/api/src/main/java/com/cloud/network/NetworkService.java
+++ b/api/src/main/java/com/cloud/network/NetworkService.java
@@ -279,4 +279,6 @@ public interface NetworkService {
     IpAddresses getIpAddressesFromIps(String ipAddress, String ip6Address, 
String macAddress);
 
     String getNicVlanValueForExternalVm(NicTO nic);
+
+    Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long 
networkId);
 }
diff --git 
a/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java 
b/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java
index 0bf06be15d8..b7fe3b26761 100644
--- a/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java
+++ b/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java
@@ -108,7 +108,7 @@ public interface LoadBalancingRulesService {
     /**
      * Assign a virtual machine or list of virtual machines, or Map of <vmId 
vmIp> to a load balancer.
      */
-    boolean assignToLoadBalancer(long lbRuleId, List<Long> vmIds, Map<Long, 
List<String>> vmIdIpMap, boolean isAutoScaleVM);
+    boolean assignToLoadBalancer(long lbRuleId, List<Long> vmIds, Map<Long, 
List<String>> vmIdIpMap, Map<Long, Long> vmIdNetworkMap, boolean isAutoScaleVM);
 
     boolean assignSSLCertToLoadBalancerRule(Long lbRuleId, String certName, 
String publicCert, String privateKey);
 
diff --git a/api/src/main/java/com/cloud/network/vpc/VpcOffering.java 
b/api/src/main/java/com/cloud/network/vpc/VpcOffering.java
index 17f49bb3652..f8460223215 100644
--- a/api/src/main/java/com/cloud/network/vpc/VpcOffering.java
+++ b/api/src/main/java/com/cloud/network/vpc/VpcOffering.java
@@ -84,4 +84,6 @@ public interface VpcOffering extends InternalIdentity, 
Identity {
     NetworkOffering.RoutingMode getRoutingMode();
 
     Boolean isSpecifyAsNumber();
+
+    boolean isConserveMode();
 }
diff --git 
a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java 
b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java
index 97b95339ecf..fbcf4f08bcc 100644
--- a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java
+++ b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java
@@ -39,7 +39,7 @@ public interface VpcProvisioningService {
                                   Map serviceCapabilitystList, 
NetUtils.InternetProtocol internetProtocol,
                                   Long serviceOfferingId, String 
externalProvider, NetworkOffering.NetworkMode networkMode,
                                   List<Long> domainIds, List<Long> zoneIds, 
VpcOffering.State state,
-                                  NetworkOffering.RoutingMode routingMode, 
boolean specifyAsNumber);
+                                  NetworkOffering.RoutingMode routingMode, 
boolean specifyAsNumber, boolean conserveMode);
 
 
     Pair<List<? extends VpcOffering>,Integer> 
listVpcOfferings(ListVPCOfferingsCmd cmd);
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java 
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index be4087a39ec..e472c986426 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -986,6 +986,7 @@ public class ApiConstants {
     public static final String REGION_ID = "regionid";
     public static final String VPC_OFF_ID = "vpcofferingid";
     public static final String VPC_OFF_NAME = "vpcofferingname";
+    public static final String VPC_OFFERING_CONSERVE_MODE = 
"vpcofferingconservemode";
     public static final String NETWORK = "network";
     public static final String VPC_ID = "vpcid";
     public static final String VPC_NAME = "vpcname";
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 6b425bc10d2..dafa72dac0a 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
@@ -161,6 +161,12 @@ public class CreateVPCOfferingCmd extends 
BaseAsyncCreateCmd {
             description = "the routing mode for the VPC offering. Supported 
types are: Static or Dynamic.")
     private String routingMode;
 
+    @Parameter(name = ApiConstants.CONSERVE_MODE, type = CommandType.BOOLEAN,
+            since = "4.23.0",
+            description = "True if the VPC offering is IP conserve mode 
enabled, allowing public IPs to be used across multiple VPC tiers. Default 
value is false")
+    private Boolean conserveMode;
+
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -311,6 +317,10 @@ public class CreateVPCOfferingCmd extends 
BaseAsyncCreateCmd {
         return routingMode;
     }
 
+    public boolean isConserveMode() {
+        return BooleanUtils.toBoolean(conserveMode);
+    }
+
     @Override
     public void create() throws ResourceAllocationException {
         VpcOffering vpcOff = _vpcProvSvc.createVpcOffering(this);
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java
index 056807b9b53..2bc5fc2ee68 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java
@@ -54,7 +54,6 @@ import com.cloud.vm.VirtualMachine;
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
 public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements 
PortForwardingRule {
 
-
     // ///////////////////////////////////////////////////
     // ////////////// API parameters /////////////////////
     // ///////////////////////////////////////////////////
@@ -278,13 +277,7 @@ public class CreatePortForwardingRuleCmd extends 
BaseAsyncCreateCmd implements P
     @Override
     public long getNetworkId() {
         IpAddress ip = _entityMgr.findById(IpAddress.class, getIpAddressId());
-        Long ntwkId = null;
-
-        if (ip.getAssociatedWithNetworkId() != null) {
-            ntwkId = ip.getAssociatedWithNetworkId();
-        } else {
-            ntwkId = networkId;
-        }
+        Long ntwkId = 
_networkService.getPreferredNetworkIdForPublicIpRuleAssignment(ip, networkId);
         if (ntwkId == null) {
             throw new InvalidParameterValueException("Unable to create port 
forwarding rule for the ipAddress id=" + ipAddressId +
                     " as ip is not associated with any network and no 
networkId is passed in");
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java
index 6d8d356cea4..cc7cd2382b7 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java
@@ -23,6 +23,8 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.network.Network;
+import com.cloud.utils.Pair;
 import com.cloud.utils.exception.CloudRuntimeException;
 
 import org.apache.cloudstack.api.APICommand;
@@ -72,7 +74,7 @@ public class AssignToLoadBalancerRuleCmd extends BaseAsyncCmd 
{
 
     @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID_IP,
             type = CommandType.MAP,
-            description = "VM ID and IP map, vmidipmap[0].vmid=1 
vmidipmap[0].vmip=10.1.1.75",
+            description = "VM ID and IP map, vmidipmap[0].vmid=1 
vmidipmap[0].vmip=10.1.1.75. (Optional, for VPC Conserve Mode) Pass 
vmnetworkid. Example: vmidipmap[0].vmnetworkid=NETWORK_TIER_UUID",
             since = "4.4")
     private Map vmIdIpMap;
 
@@ -116,8 +118,9 @@ public class AssignToLoadBalancerRuleCmd extends 
BaseAsyncCmd {
     }
 
 
-    public Map<Long, List<String>> getVmIdIpListMap() {
-        Map<Long, List<String>> vmIdIpsMap = new HashMap<Long, List<String>>();
+    public Pair<Map<Long, List<String>>, Map<Long, Long>> 
getVmIdIpListMapAndVmIdNetworkMap() {
+        Map<Long, List<String>> vmIdIpsMap = new HashMap<>();
+        Map<Long, Long> vmIdNetworkMap = new HashMap<>();
         if (vmIdIpMap != null && !vmIdIpMap.isEmpty()) {
             Collection idIpsCollection = vmIdIpMap.values();
             Iterator iter = idIpsCollection.iterator();
@@ -125,6 +128,7 @@ public class AssignToLoadBalancerRuleCmd extends 
BaseAsyncCmd {
                 HashMap<String, String> idIpsMap = (HashMap<String, 
String>)iter.next();
                 String vmId = idIpsMap.get("vmid");
                 String vmIp = idIpsMap.get("vmip");
+                String vmNetworkUuid = idIpsMap.get("vmnetworkid");
 
                 VirtualMachine lbvm = 
_entityMgr.findByUuid(VirtualMachine.class, vmId);
                 if (lbvm == null) {
@@ -145,25 +149,35 @@ public class AssignToLoadBalancerRuleCmd extends 
BaseAsyncCmd {
                     ipsList = new ArrayList<String>();
                 }
                 ipsList.add(vmIp);
+
+                if (vmNetworkUuid != null) {
+                    Network vmNetwork = _entityMgr.findByUuid(Network.class, 
vmNetworkUuid);
+                    if (vmNetwork == null) {
+                        throw new InvalidParameterValueException("Unable to 
find Network ID: " + vmNetworkUuid);
+                    }
+                    vmIdNetworkMap.put(longVmId, vmNetwork.getId());
+                }
                 vmIdIpsMap.put(longVmId, ipsList);
 
             }
         }
 
-        return vmIdIpsMap;
+        return new Pair<>(vmIdIpsMap, vmIdNetworkMap);
     }
 
     @Override
     public void execute() {
         CallContext.current().setEventDetails("Load balancer ID: " + 
getResourceUuid(ApiConstants.ID) + " Instances IDs: " + 
StringUtils.join(getVirtualMachineIds(), ","));
 
-        Map<Long, List<String>> vmIdIpsMap = getVmIdIpListMap();
+        Pair<Map<Long, List<String>>, Map<Long, Long>> mapsPair = 
getVmIdIpListMapAndVmIdNetworkMap();
+        Map<Long, List<String>> vmIdIpsMap = mapsPair.first();
+        Map<Long, Long> vmIdNetworkMap = mapsPair.second();
         boolean result = false;
 
         try {
-            result = _lbService.assignToLoadBalancer(getLoadBalancerId(), 
virtualMachineIds, vmIdIpsMap, false);
+            result = _lbService.assignToLoadBalancer(getLoadBalancerId(), 
virtualMachineIds, vmIdIpsMap, vmIdNetworkMap, false);
         }catch (CloudRuntimeException ex) {
-            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed 
to assign load balancer rule");
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed 
to assign load balancer rule due to: " + ex.getMessage());
         }
 
         if (result) {
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java
 
b/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java
index 48097e51d99..aed56a36908 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java
@@ -94,6 +94,10 @@ public class FirewallRuleResponse extends BaseResponse {
     @Param(description = "The ID of the guest Network the port forwarding rule 
belongs to")
     private String networkId;
 
+    @SerializedName(ApiConstants.NETWORK_NAME)
+    @Param(description = "The Name of the guest Network the port forwarding 
rule belongs to")
+    private String networkName;
+
     @SerializedName(ApiConstants.FOR_DISPLAY)
     @Param(description = "Is firewall for display to the regular user", since 
= "4.4", authorized = {RoleType.Admin})
     private Boolean forDisplay;
@@ -223,6 +227,10 @@ public class FirewallRuleResponse extends BaseResponse {
         this.networkId = networkId;
     }
 
+    public void setNetworkName(String networkName) {
+        this.networkName = networkName;
+    }
+
     public void setForDisplay(Boolean forDisplay) {
         this.forDisplay = forDisplay;
     }
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java
index a0516e660e4..2e821dae52d 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java
@@ -102,6 +102,10 @@ public class VpcOfferingResponse extends BaseResponse {
     @Param(description = "The routing mode for the network offering, supported 
types are Static or Dynamic.")
     private String routingMode;
 
+    @SerializedName(ApiConstants.CONSERVE_MODE)
+    @Param(description = "True if the VPC offering is IP conserve mode 
enabled, allowing public IP services to be used across multiple VPC tiers.", 
since = "4.23.0")
+    private Boolean conserveMode;
+
     public void setId(String id) {
         this.id = id;
     }
@@ -201,4 +205,12 @@ public class VpcOfferingResponse extends BaseResponse {
     public void setRoutingMode(String routingMode) {
         this.routingMode = routingMode;
     }
+
+    public Boolean getConserveMode() {
+        return conserveMode;
+    }
+
+    public void setConserveMode(Boolean conserveMode) {
+        this.conserveMode = conserveMode;
+    }
 }
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java
index 2648ba83678..acfabb11350 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java
@@ -73,6 +73,10 @@ public class VpcResponse extends BaseResponseWithAnnotations 
implements Controll
     @Param(description = "VPC offering name the VPC is created from", since = 
"4.13.2")
     private String vpcOfferingName;
 
+    @SerializedName(ApiConstants.VPC_OFFERING_CONSERVE_MODE)
+    @Param(description = "true if VPC offering is ip conserve mode enabled", 
since = "4.23")
+    private Boolean vpcOfferingConserveMode;
+
     @SerializedName(ApiConstants.CREATED)
     @Param(description = "The date this VPC was created")
     private Date created;
@@ -197,6 +201,10 @@ public class VpcResponse extends 
BaseResponseWithAnnotations implements Controll
         this.displayText = displayText;
     }
 
+    public void setVpcOfferingConserveMode(Boolean vpcOfferingConserveMode) {
+        this.vpcOfferingConserveMode = vpcOfferingConserveMode;
+    }
+
     public void setCreated(final Date created) {
         this.created = created;
     }
diff --git 
a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java 
b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java
index b1cad20b19e..454cb10a2f2 100644
--- 
a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java
+++ 
b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java
@@ -288,4 +288,6 @@ public interface IpAddressManager {
     PublicIpQuarantine updatePublicIpAddressInQuarantine(Long 
quarantineProcessId, Date endDate);
 
     void updateSourceNatIpAddress(IPAddressVO requestedIp, List<IPAddressVO> 
userIps) throws Exception;
+
+    Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long 
networkId);
 }
diff --git 
a/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java
 
b/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java
index 669456cbdcc..d8011e9ade1 100644
--- 
a/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java
+++ 
b/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java
@@ -39,7 +39,7 @@ import com.cloud.user.Account;
 public interface LoadBalancingRulesManager {
 
     LoadBalancer createPublicLoadBalancer(String xId, String name, String 
description, int srcPort, int destPort, long sourceIpId, String protocol, 
String algorithm,
-        boolean openFirewall, CallContext caller, String lbProtocol, Boolean 
forDisplay, String cidrList) throws NetworkRuleConflictException;
+        boolean openFirewall, CallContext caller, String lbProtocol, Boolean 
forDisplay, String cidrList, Long networkId) throws 
NetworkRuleConflictException;
 
     boolean removeAllLoadBalanacersForIp(long ipId, Account caller, long 
callerUserId);
 
diff --git 
a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java 
b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java
index e7f41d079a7..792a3a6b397 100644
--- a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java
+++ b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java
@@ -211,4 +211,9 @@ public interface VpcManager {
     void reconfigStaticNatForVpcVr(Long vpcId);
 
     boolean applyStaticRouteForVpcVpnIfNeeded(Long vpcId, boolean 
updateAllVpn) throws ResourceUnavailableException;
+
+    /**
+     * Returns true if the network is part of a VPC, and the VPC is created 
from conserve mode enabled VPC offering
+     */
+    boolean isNetworkOnVpcEnabledConserveMode(Network network);
 }
diff --git 
a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java 
b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java
index 9320a37bc96..b913468384e 100644
--- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java
+++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java
@@ -91,6 +91,9 @@ public class VpcOfferingVO implements VpcOffering {
     @Column(name = "specify_as_number")
     private Boolean specifyAsNumber = false;
 
+    @Column(name = "conserve_mode")
+    private boolean conserveMode;
+
     public VpcOfferingVO() {
         this.uuid = UUID.randomUUID().toString();
     }
@@ -242,4 +245,13 @@ public class VpcOfferingVO implements VpcOffering {
     public void setSpecifyAsNumber(Boolean specifyAsNumber) {
         this.specifyAsNumber = specifyAsNumber;
     }
+
+    @Override
+    public boolean isConserveMode() {
+        return conserveMode;
+    }
+
+    public void setConserveMode(boolean conserveMode) {
+        this.conserveMode = conserveMode;
+    }
 }
diff --git 
a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql 
b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql
index 6d02bc314f7..d69b524b85d 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql
@@ -111,3 +111,6 @@ ALTER TABLE `cloud`.`user` DROP COLUMN api_key, DROP COLUMN 
secret_key;
 CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('User', 'deleteUserKeys', 
'ALLOW');
 CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Domain Admin', 
'deleteUserKeys', 'ALLOW');
 CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 
'deleteUserKeys', 'ALLOW');
+
+-- Add conserve mode for VPC offerings
+CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 
'tinyint(1) unsigned NULL DEFAULT 0 COMMENT ''True if the VPC offering is IP 
conserve mode enabled, allowing public IP services to be used across multiple 
VPC tiers'' ');
diff --git 
a/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql
 
b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql
index 751d8f91a25..3669bb10122 100644
--- 
a/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql
+++ 
b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql
@@ -38,6 +38,7 @@ select
     `vpc_offerings`.`sort_key` AS `sort_key`,
     `vpc_offerings`.`routing_mode` AS `routing_mode`,
     `vpc_offerings`.`specify_as_number` AS `specify_as_number`,
+    `vpc_offerings`.`conserve_mode` AS `conserve_mode`,
     group_concat(distinct `domain`.`id` separator ',') AS `domain_id`,
     group_concat(distinct `domain`.`uuid` separator ',') AS `domain_uuid`,
     group_concat(distinct `domain`.`name` separator ',') AS `domain_name`,
diff --git 
a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java
 
b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java
index 9ffef2c0b32..1ed75f14dfb 100644
--- 
a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java
+++ 
b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java
@@ -693,7 +693,7 @@ public class KubernetesClusterResourceModifierActionWorker 
extends KubernetesClu
             ips.add(controlVmNic.getIPv4Address());
             vmIdIpMap.put(clusterVMIds.get(i), ips);
         }
-        lbService.assignToLoadBalancer(lb.getId(), null, vmIdIpMap, false);
+        lbService.assignToLoadBalancer(lb.getId(), null, vmIdIpMap, null, 
false);
     }
 
     protected Map<Long, Integer> createFirewallRules(IpAddress publicIp, 
List<Long> clusterVMIds, boolean apiRule) throws ManagementServerException {
diff --git 
a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/LoadBalanceRuleHandler.java
 
b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/LoadBalanceRuleHandler.java
index 3df58470fc6..fc167b71c23 100644
--- 
a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/LoadBalanceRuleHandler.java
+++ 
b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/LoadBalanceRuleHandler.java
@@ -363,7 +363,7 @@ public class LoadBalanceRuleHandler {
             lb.setSourceIpAddressId(ipId);
 
             result = _lbMgr.createPublicLoadBalancer(lb.getXid(), 
lb.getName(), lb.getDescription(), lb.getSourcePortStart(), 
lb.getDefaultPortStart(), ipId.longValue(),
-                    lb.getProtocol(), lb.getAlgorithm(), false, 
CallContext.current(), lb.getLbProtocol(), true, null);
+                    lb.getProtocol(), lb.getAlgorithm(), false, 
CallContext.current(), lb.getLbProtocol(), true, null, networkId);
         } catch (final NetworkRuleConflictException e) {
             logger.warn("Failed to create LB rule, not continuing with ELB 
deployment");
             if (newIp) {
diff --git 
a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java
 
b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java
index f360fab0112..8badb916eed 100644
--- 
a/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java
+++ 
b/plugins/network-elements/juniper-contrail/src/main/java/org/apache/cloudstack/network/contrail/management/ContrailManagerImpl.java
@@ -293,7 +293,7 @@ public class ContrailManagerImpl extends ManagerBase 
implements ContrailManager
             }
             serviceProviderMap.put(svc, providerSet);
         }
-        vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, 
juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, 
null, null, null, null, VpcOffering.State.Enabled, null, false);
+        vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, 
juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, 
null, null, null, null, VpcOffering.State.Enabled, null, false, false);
         long id = vpcOffer.getId();
         _vpcOffDao.update(id, (VpcOfferingVO)vpcOffer);
         return _vpcOffDao.findById(id);
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java 
b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index 51cf82b13b0..cf98df0da24 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -1675,7 +1675,7 @@ public class ApiResponseHelper implements 
ResponseGenerator {
 
         Network guestNtwk = ApiDBUtils.findNetworkById(fwRule.getNetworkId());
         response.setNetworkId(guestNtwk.getUuid());
-
+        response.setNetworkName(guestNtwk.getName());
 
         IpAddress ip = 
ApiDBUtils.findIpAddressById(fwRule.getSourceIpAddressId());
 
@@ -3535,6 +3535,7 @@ public class ApiResponseHelper implements 
ResponseGenerator {
         if (voff != null) {
             response.setVpcOfferingId(voff.getUuid());
             response.setVpcOfferingName(voff.getName());
+            response.setVpcOfferingConserveMode(voff.isConserveMode());
         }
         response.setCidr(vpc.getCidr());
         response.setRestartRequired(vpc.isRestartRequired());
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImpl.java
index 7ea4b7d5834..e7fe07a18c7 100644
--- a/server/src/main/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/VpcOfferingJoinDaoImpl.java
@@ -77,6 +77,7 @@ public class VpcOfferingJoinDaoImpl extends 
GenericDaoBase<VpcOfferingJoinVO, Lo
         if (offering.isSpecifyAsNumber() != null) {
             offeringResponse.setSpecifyAsNumber(offering.isSpecifyAsNumber());
         }
+        offeringResponse.setConserveMode(offering.isConserveMode());
         if (offering instanceof VpcOfferingJoinVO) {
             VpcOfferingJoinVO offeringJoinVO = (VpcOfferingJoinVO) offering;
             offeringResponse.setDomainId(offeringJoinVO.getDomainUuid());
diff --git a/server/src/main/java/com/cloud/api/query/vo/VpcOfferingJoinVO.java 
b/server/src/main/java/com/cloud/api/query/vo/VpcOfferingJoinVO.java
index 4e0707edf88..9d65c19479f 100644
--- a/server/src/main/java/com/cloud/api/query/vo/VpcOfferingJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/VpcOfferingJoinVO.java
@@ -112,6 +112,9 @@ public class VpcOfferingJoinVO implements VpcOffering {
     @Column(name = "specify_as_number")
     private Boolean specifyAsNumber = false;
 
+    @Column(name = "conserve_mode")
+    private boolean conserveMode;
+
     public VpcOfferingJoinVO() {
     }
 
@@ -178,6 +181,11 @@ public class VpcOfferingJoinVO implements VpcOffering {
         return specifyAsNumber;
     }
 
+    @Override
+    public boolean isConserveMode() {
+        return conserveMode;
+    }
+
     public void setSpecifyAsNumber(Boolean specifyAsNumber) {
         this.specifyAsNumber = specifyAsNumber;
     }
diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java 
b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
index 20ca189994e..7f41a1a106c 100644
--- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java
@@ -1543,6 +1543,14 @@ public class IpAddressManagerImpl extends ManagerBase 
implements IpAddressManage
         return ipaddr;
     }
 
+    protected IPAddressVO getExistingSourceNatInVPC(Long vpcId) {
+        List<IPAddressVO> ips = _ipAddressDao.listByAssociatedVpc(vpcId, true);
+        if (CollectionUtils.isEmpty(ips)) {
+            return null;
+        }
+        return ips.get(0);
+    }
+
     protected IPAddressVO getExistingSourceNatInNetwork(long ownerId, Long 
networkId) {
         List<? extends IpAddress> addrs;
         Network guestNetwork = _networksDao.findById(networkId);
@@ -1723,7 +1731,11 @@ public class IpAddressManagerImpl extends ManagerBase 
implements IpAddressManage
         NetworkOffering offering = 
_networkOfferingDao.findById(network.getNetworkOfferingId());
         boolean sharedSourceNat = offering.isSharedSourceNat();
         boolean isSourceNat = false;
-        if (!sharedSourceNat) {
+        if (network.getVpcId() != null) {
+            // For VPCs: Check if the VPC Source NAT IP address is the same we 
are associating
+            IPAddressVO vpcSourceNatIpAddress = 
getExistingSourceNatInVPC(network.getVpcId());
+            isSourceNat = vpcSourceNatIpAddress != null && 
vpcSourceNatIpAddress.getId() == ipToAssoc.getId();
+        } else if (!sharedSourceNat) {
             if (getExistingSourceNatInNetwork(owner.getId(), network.getId()) 
== null) {
                 if (network.getGuestType() == GuestType.Isolated && 
network.getVpcId() == null && !ipToAssoc.isPortable()) {
                     isSourceNat = true;
@@ -2647,4 +2659,31 @@ public class IpAddressManagerImpl extends ManagerBase 
implements IpAddressManage
         });
     }
 
+    @Override
+    public Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, 
Long networkId) {
+        boolean vpcConserveMode = isPublicIpOnVpcConserveMode(ip);
+        return getPreferredNetworkIdForRule(ip, vpcConserveMode, networkId);
+    }
+
+    protected Long getPreferredNetworkIdForRule(IpAddress ip, boolean 
vpcConserveModeEnabled, Long networkId) {
+        if (vpcConserveModeEnabled) {
+            // Since VPC Conserve mode allows rules from multiple VPC tiers, 
always check the networkId parameter first
+            return networkId != null ? networkId : 
ip.getAssociatedWithNetworkId();
+        } else {
+            // In case of Guest Networks or VPC Tier Networks VPC Conserve 
mode disabled prefer the associated networkId
+            return ip.getAssociatedWithNetworkId() != null ? 
ip.getAssociatedWithNetworkId() : networkId;
+        }
+    }
+
+    protected boolean isPublicIpOnVpcConserveMode(IpAddress ip) {
+        if (ip.getVpcId() == null) {
+            return false;
+        }
+        Vpc vpc =  _vpcMgr.getActiveVpc(ip.getVpcId());
+        if (vpc == null) {
+            return false;
+        }
+        VpcOffering vpcOffering = 
vpcOfferingDao.findById(vpc.getVpcOfferingId());
+        return vpcOffering != null && vpcOffering.isConserveMode();
+    }
 }
diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java 
b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
index b959cc478d6..0a2e679b723 100644
--- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
+++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
@@ -6434,6 +6434,11 @@ public class NetworkServiceImpl extends ManagerBase 
implements NetworkService, C
         return Networks.BroadcastDomainType.getValue(nic.getBroadcastUri());
     }
 
+    @Override
+    public Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, 
Long networkId) {
+        return _ipAddrMgr.getPreferredNetworkIdForPublicIpRuleAssignment(ip, 
networkId);
+    }
+
     @Override
     public Network.IpAddresses getIpAddressesFromIps(String ipAddress, String 
ip6Address, String macAddress) {
         if (ip6Address != null) {
diff --git 
a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java 
b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
index 805a897e080..805ac4aed86 100644
--- a/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/as/AutoScaleManagerImpl.java
@@ -2051,7 +2051,7 @@ public class AutoScaleManagerImpl extends ManagerBase 
implements AutoScaleManage
         }
         lstVmId.add(new Long(vmId));
         try {
-            return loadBalancingRulesService.assignToLoadBalancer(lbId, 
lstVmId, new HashMap<>(), true);
+            return loadBalancingRulesService.assignToLoadBalancer(lbId, 
lstVmId, new HashMap<>(), null, true);
         } catch (CloudRuntimeException ex) {
             logger.warn("Caught exception: ", ex);
             return false;
diff --git 
a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java 
b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java
index 00863c28dd2..779d26d51f1 100644
--- a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java
@@ -30,6 +30,8 @@ import java.util.Set;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.network.vpc.Vpc;
+import com.cloud.network.vpc.dao.VpcOfferingDao;
 import org.apache.commons.lang3.ObjectUtils;
 import org.springframework.stereotype.Component;
 
@@ -159,6 +161,8 @@ public class FirewallManagerImpl extends ManagerBase 
implements FirewallService,
     IpAddressManager _ipAddrMgr;
     @Inject
     RoutedIpv4Manager routedIpv4Manager;
+    @Inject
+    VpcOfferingDao vpcOfferingDao;
 
     private boolean _elbEnabled = false;
     static Boolean rulesContinueOnErrFlag = true;
@@ -395,6 +399,10 @@ public class FirewallManagerImpl extends ManagerBase 
implements FirewallService,
             assert (rules.size() >= 1);
         }
 
+        NetworkVO newRuleNetwork = getNewRuleNetwork(newRule);
+        boolean newRuleIsOnVpcNetwork = newRuleNetwork.getVpcId() != null;
+        boolean vpcConserveModeEnabled = 
_vpcMgr.isNetworkOnVpcEnabledConserveMode(newRuleNetwork);
+
         for (FirewallRuleVO rule : rules) {
             if (rule.getId() == newRule.getId()) {
                 continue; // Skips my own rule.
@@ -443,8 +451,15 @@ public class FirewallManagerImpl extends ManagerBase 
implements FirewallService,
             }
 
             // Checking if the rule applied is to the same network that is 
passed in the rule.
-            if (rule.getNetworkId() != newRule.getNetworkId() && 
rule.getState() != State.Revoke) {
-                throw new NetworkRuleConflictException("New rule is for a 
different network than what's specified in rule " + rule.getXid());
+            // (except for VPCs with conserve mode = true)
+            if ((!newRuleIsOnVpcNetwork || !vpcConserveModeEnabled)
+                    && rule.getNetworkId() != newRule.getNetworkId() && 
rule.getState() != State.Revoke) {
+                String errMsg = String.format("New rule is for a different 
network than what's specified in rule %s", rule.getXid());
+                if (newRuleIsOnVpcNetwork) {
+                    Vpc vpc = _vpcMgr.getActiveVpc(newRuleNetwork.getVpcId());
+                    errMsg += String.format(" - VPC id=%s is not using 
conserve mode", vpc.getUuid());
+                }
+                throw new NetworkRuleConflictException(errMsg);
             }
 
             //Check for the ICMP protocol. This has to be done separately from 
other protocols as we need to check the ICMP codes and ICMP type also.
@@ -493,6 +508,14 @@ public class FirewallManagerImpl extends ManagerBase 
implements FirewallService,
         }
     }
 
+    protected NetworkVO getNewRuleNetwork(FirewallRule newRule) {
+        NetworkVO newRuleNetwork = 
_networkDao.findById(newRule.getNetworkId());
+        if (newRuleNetwork == null) {
+            throw new InvalidParameterValueException("Unable to create 
firewall rule as cannot find network by id=" + newRule.getNetworkId());
+        }
+        return newRuleNetwork;
+    }
+
     protected boolean checkIfRulesHaveConflictingPortRanges(FirewallRule 
newRule, FirewallRule rule, boolean oneOfRulesIsFirewall, boolean 
bothRulesFirewall, boolean bothRulesPortForwarding, boolean duplicatedCidrs) {
         String rulesAsString = String.format("[%s] and [%s]", rule, newRule);
 
diff --git 
a/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java 
b/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java
index ced1d781ab5..5f00261f329 100644
--- 
a/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java
+++ 
b/server/src/main/java/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java
@@ -53,6 +53,8 @@ import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO;
 import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao;
 import 
org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 
 import com.cloud.agent.api.to.LoadBalancerTO;
@@ -1018,7 +1020,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends 
ManagerBase implements
     @Override
     @DB
     @ActionEvent(eventType = EventTypes.EVENT_ASSIGN_TO_LOAD_BALANCER_RULE, 
eventDescription = "assigning to load balancer", async = true)
-    public boolean assignToLoadBalancer(long loadBalancerId, List<Long> 
instanceIds, Map<Long, List<String>> vmIdIpMap, boolean isAutoScaleVM) {
+    public boolean assignToLoadBalancer(long loadBalancerId, List<Long> 
instanceIds, Map<Long, List<String>> vmIdIpMap, Map<Long, Long> vmIdNetworkMap, 
boolean isAutoScaleVM) {
         CallContext ctx = CallContext.current();
         Account caller = ctx.getCallingAccount();
 
@@ -1091,28 +1093,12 @@ public class LoadBalancingRulesManagerImpl<Type> 
extends ManagerBase implements
             _rulesMgr.checkRuleAndUserVm(loadBalancer, vm, caller);
 
             Account vmOwner = _accountDao.findById(vm.getAccountId());
-            Network network = 
_networkDao.findById(loadBalancer.getNetworkId());
-            _accountMgr.checkAccess(vmOwner, 
SecurityChecker.AccessType.UseEntry, false, network);
-
-            // Let's check to make sure the vm has a nic in the same network as
-            // the load balancing rule.
-            List<? extends Nic> nics = _networkModel.getNics(vm.getId());
-            Nic nicInSameNetwork = null;
-            for (Nic nic : nics) {
-                if (nic.getNetworkId() == loadBalancer.getNetworkId()) {
-                    nicInSameNetwork = nic;
-                    break;
-                }
-            }
+            Network loadBalancerNetwork = 
_networkDao.findById(loadBalancer.getNetworkId());
+            _accountMgr.checkAccess(vmOwner, 
SecurityChecker.AccessType.UseEntry, false, loadBalancerNetwork);
 
-            if (nicInSameNetwork == null) {
-                InvalidParameterValueException ex =
-                        new InvalidParameterValueException("VM with id 
specified cannot be added because it doesn't belong in the same network.");
-                ex.addProxyObject(vm.getUuid(), "instanceId");
-                throw ex;
-            }
+            Nic vmNicInLb = getVmNicInLoadBalancer(vm, loadBalancer, 
loadBalancerNetwork, vmIdNetworkMap, vmOwner);
 
-            String priIp = nicInSameNetwork.getIPv4Address();
+            String priIp = vmNicInLb.getIPv4Address();
 
             if (existingVmIdIps.containsKey(instanceId)) {
                 // now check for ip address
@@ -1142,9 +1128,9 @@ public class LoadBalancingRulesManagerImpl<Type> extends 
ManagerBase implements
                     if (ip.equals(priIp)) {
                         continue;
                     }
-                    
if(_nicSecondaryIpDao.findByIp4AddressAndNicId(ip,nicInSameNetwork.getId()) == 
null) {
+                    
if(_nicSecondaryIpDao.findByIp4AddressAndNicId(ip,vmNicInLb.getId()) == null) {
                         throw new InvalidParameterValueException("Instance IP 
"+ ip + " specified does not belong to " +
-                                "NIC in Network " + 
nicInSameNetwork.getNetworkId());
+                                "NIC in Network " + vmNicInLb.getNetworkId());
                     }
                 }
             } else {
@@ -1234,6 +1220,63 @@ public class LoadBalancingRulesManagerImpl<Type> extends 
ManagerBase implements
         return success;
     }
 
+    protected Nic getVmNicInLoadBalancer(UserVm vm, LoadBalancerVO 
loadBalancer, Network loadBalancerNetwork, Map<Long, Long> vmIdNetworkMap, 
Account vmOwner) {
+        boolean isVpcConserveModeEnabled = 
_vpcMgr.isNetworkOnVpcEnabledConserveMode(loadBalancerNetwork);
+
+        boolean isNetworkPassedVpcConserveMode = isVpcConserveModeEnabled && 
MapUtils.isNotEmpty(vmIdNetworkMap) && vmIdNetworkMap.containsKey(vm.getId());
+        Nic vmNicInLb = isNetworkPassedVpcConserveMode ?
+                getNicForVmInVpcConserveModeTierNetwork(vm, vmIdNetworkMap, 
vmOwner, loadBalancerNetwork) :
+                getNicForVmLbNetwork(vm, loadBalancer);
+
+        if (vmNicInLb == null) {
+            String msg = !isVpcConserveModeEnabled ?
+                    "VM with id specified cannot be added because it doesn't 
belong in the same network." :
+                    "VM with id specified cannot be added to the load 
balancing rule for VPC Conserve Mode.";
+            InvalidParameterValueException ex = new 
InvalidParameterValueException(msg);
+            ex.addProxyObject(vm.getUuid(), "instanceId");
+            throw ex;
+        }
+        return vmNicInLb;
+    }
+
+    /**
+     * For Isolated Networks or Network tiers of VPCs not using Conserve mode, 
use the same network as the load balancer
+     * @return the nic of the VM in the load balancer network
+     */
+    protected Nic getNicForVmLbNetwork(UserVm vm, LoadBalancerVO loadBalancer) 
{
+        List<? extends Nic> nics = _networkModel.getNics(vm.getId());
+        for (Nic nic : nics) {
+            if (nic.getNetworkId() == loadBalancer.getNetworkId()) {
+                return nic;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * On VPC Conserve Mode, VMs from multiple VPC networks tiers can be 
assigned to the same load balancer.
+     * @return the nic of the VM in the specified tier network in 
`vmIdNetworkMap`
+     */
+    protected Nic getNicForVmInVpcConserveModeTierNetwork(UserVm vm, Map<Long, 
Long> vmIdNetworkMap, Account vmOwner, Network loadBalancerNetwork) {
+        Long vmNetworkId = vmIdNetworkMap.get(vm.getId());
+        Network vmNetwork = _networkDao.findById(vmNetworkId);
+        _accountMgr.checkAccess(vmOwner, SecurityChecker.AccessType.UseEntry, 
false, vmNetwork);
+        checkNetworkBelongsToLoadBalancerVpc(vmNetwork, loadBalancerNetwork);
+        return _networkModel.getNicInNetwork(vm.getId(), vmNetworkId);
+    }
+
+    protected void checkNetworkBelongsToLoadBalancerVpc(Network vmNetwork, 
Network loadBalancerNetwork) {
+        if (ObjectUtils.anyNull(vmNetwork, loadBalancerNetwork)) {
+            throw new InvalidParameterValueException("Cannot add VM to load 
balancer because the VM network or load balancer network is null");
+        }
+        if (ObjectUtils.anyNull(vmNetwork.getVpcId(), 
loadBalancerNetwork.getVpcId())) {
+            throw new InvalidParameterValueException("Cannot add VM to load 
balancer because the VM network or load balancer network are not part of a 
VPC");
+        }
+        if (!vmNetwork.getVpcId().equals(loadBalancerNetwork.getVpcId())) {
+            throw new InvalidParameterValueException("Cannot add VM to load 
balancer because the VM network and load balancer network are not part of the 
same VPC");
+        }
+    }
+
     @Override
     public boolean assignSSLCertToLoadBalancerRule(Long lbId, String certName, 
String publicCert, String privateKey) {
         logger.error("Calling the manager for LB");
@@ -1740,6 +1783,8 @@ public class LoadBalancingRulesManagerImpl<Type> extends 
ManagerBase implements
             throw new NetworkRuleConflictException("Can't do load balance on 
IP address: " + ipVO.getAddress());
         }
 
+        verifyLoadBalancerRuleNetwork(name, network, ipVO);
+
         String cidrString = generateCidrString(cidrList);
 
         boolean performedIpAssoc = false;
@@ -1763,7 +1808,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends 
ManagerBase implements
             }
 
             result = createPublicLoadBalancer(xId, name, description, 
srcPortStart, defPortStart, ipVO.getId(), protocol, algorithm, openFirewall, 
CallContext.current(),
-                    lbProtocol, forDisplay, cidrString);
+                    lbProtocol, forDisplay, cidrString, networkId);
         } catch (Exception ex) {
             logger.warn("Failed to create load balancer due to ", ex);
             if (ex instanceof NetworkRuleConflictException) {
@@ -1792,7 +1837,18 @@ public class LoadBalancingRulesManagerImpl<Type> extends 
ManagerBase implements
 
         return result;
     }
-   /**
+
+    protected void verifyLoadBalancerRuleNetwork(String lbName, Network 
network, IPAddressVO ipVO) {
+        boolean isVpcConserveModeEnabled = 
_vpcMgr.isNetworkOnVpcEnabledConserveMode(network);
+        if (!isVpcConserveModeEnabled && ipVO.getAssociatedWithNetworkId() != 
null && network.getId() != ipVO.getAssociatedWithNetworkId()) {
+            String msg = String.format("Cannot create Load Balancer rule %s as 
the IP address %s is not associated " +
+                    "with the network %s (ID=%s)", lbName, ipVO.getAddress(), 
network.getName(), network.getUuid());
+            logger.error(msg);
+            throw new InvalidParameterValueException(msg);
+        }
+    }
+
+    /**
     * Transforms the cidrList from a List of Strings to a String which 
contains all the CIDRs from cidrList separated by whitespaces. This is used to 
facilitate both the persistence
     * in the DB and also later when building the configuration String in the 
getRulesForPool method of the HAProxyConfigurator class.
    */
@@ -1826,7 +1882,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends 
ManagerBase implements
     @Override
     public LoadBalancer createPublicLoadBalancer(final String xId, final 
String name, final String description, final int srcPort, final int destPort, 
final long sourceIpId,
                                                  final String protocol, final 
String algorithm, final boolean openFirewall, final CallContext caller, final 
String lbProtocol,
-                                                 final Boolean forDisplay, 
String cidrList) throws NetworkRuleConflictException {
+                                                 final Boolean forDisplay, 
String cidrList, Long networkIdParam) throws NetworkRuleConflictException {
         if (!NetUtils.isValidPort(destPort)) {
             throw new InvalidParameterValueException("privatePort is an 
invalid value: " + destPort);
         }
@@ -1855,7 +1911,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends 
ManagerBase implements
 
             _accountMgr.checkAccess(caller.getCallingAccount(), null, true, 
ipAddr);
 
-            final Long networkId = ipAddr.getAssociatedWithNetworkId();
+            final Long networkId = 
_ipAddrMgr.getPreferredNetworkIdForPublicIpRuleAssignment(ipAddr, 
networkIdParam);
             if (networkId == null) {
                 InvalidParameterValueException ex =
                         new InvalidParameterValueException("Unable to create 
load balancer rule ; specified sourceip id is not associated with any network");
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 86d1fba038b..3c3afb34e5a 100644
--- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
+++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java
@@ -388,7 +388,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
                     }
                     createVpcOffering(VpcOffering.defaultVPCOfferingName, 
VpcOffering.defaultVPCOfferingName, svcProviderMap,
                             true, State.Enabled, null, false,
-                            false, false, null, null, false);
+                            false, false, null, null, false, false);
                 }
 
                 // configure default vpc offering with Netscaler as LB Provider
@@ -408,7 +408,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
                         }
                     }
                     createVpcOffering(VpcOffering.defaultVPCNSOfferingName, 
VpcOffering.defaultVPCNSOfferingName,
-                            svcProviderMap, false, State.Enabled, null, false, 
false, false, null, null, false);
+                            svcProviderMap, false, State.Enabled, null, false, 
false, false, null, null, false, false);
 
                 }
 
@@ -429,7 +429,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
                         }
                     }
                     createVpcOffering(VpcOffering.redundantVPCOfferingName, 
VpcOffering.redundantVPCOfferingName, svcProviderMap, true, State.Enabled,
-                            null, false, false, true, null, null, false);
+                            null, false, false, true, null, null, false, 
false);
                 }
 
                 // configure default vpc offering with NSX as network service 
provider in NAT mode
@@ -446,7 +446,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
                         }
                     }
                     
createVpcOffering(VpcOffering.DEFAULT_VPC_NAT_NSX_OFFERING_NAME, 
VpcOffering.DEFAULT_VPC_NAT_NSX_OFFERING_NAME, svcProviderMap, false,
-                            State.Enabled, null, false, false, false, 
NetworkOffering.NetworkMode.NATTED, null, false);
+                            State.Enabled, null, false, false, false, 
NetworkOffering.NetworkMode.NATTED, null, false, false);
 
                 }
 
@@ -464,7 +464,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
                         }
                     }
                     
createVpcOffering(VpcOffering.DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME, 
VpcOffering.DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME, svcProviderMap, false,
-                            State.Enabled, null, false, false, false, 
NetworkOffering.NetworkMode.ROUTED, null, false);
+                            State.Enabled, null, false, false, false, 
NetworkOffering.NetworkMode.ROUTED, null, false, false);
 
                 }
 
@@ -482,7 +482,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
                         }
                     }
                     
createVpcOffering(VpcOffering.DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME, 
VpcOffering.DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME, svcProviderMap, false,
-                            State.Enabled, null, false, false, false, 
NetworkOffering.NetworkMode.ROUTED, null, false);
+                            State.Enabled, null, false, false, false, 
NetworkOffering.NetworkMode.ROUTED, null, false, false);
 
                 }
 
@@ -500,7 +500,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
                         }
                     }
                     
createVpcOffering(VpcOffering.DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME, 
VpcOffering.DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME, svcProviderMap, false,
-                            State.Enabled, null, false, false, false, 
NetworkOffering.NetworkMode.NATTED, null, false);
+                            State.Enabled, null, false, false, false, 
NetworkOffering.NetworkMode.NATTED, null, false, false);
 
                 }
             }
@@ -586,6 +586,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
         }
         boolean specifyAsNumber = cmd.getSpecifyAsNumber();
         String routingModeString = cmd.getRoutingMode();
+        boolean conserveMode = cmd.isConserveMode();
 
         // check if valid domain
         if (CollectionUtils.isNotEmpty(cmd.getDomainIds())) {
@@ -624,7 +625,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
 
         return createVpcOffering(vpcOfferingName, displayText, 
supportedServices,
                 serviceProviderList, serviceCapabilityList, internetProtocol, 
serviceOfferingId, provider, networkMode,
-                domainIds, zoneIds, (enable ? State.Enabled : State.Disabled), 
routingMode, specifyAsNumber);
+                domainIds, zoneIds, (enable ? State.Enabled : State.Disabled), 
routingMode, specifyAsNumber, conserveMode);
     }
 
     @Override
@@ -632,7 +633,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
     public VpcOffering createVpcOffering(final String name, final String 
displayText, final List<String> supportedServices, final Map<String, 
List<String>> serviceProviders,
                                          final Map serviceCapabilityList, 
final NetUtils.InternetProtocol internetProtocol, final Long serviceOfferingId,
                                          final String externalProvider, final 
NetworkOffering.NetworkMode networkMode, List<Long> domainIds, List<Long> 
zoneIds, State state,
-                                         NetworkOffering.RoutingMode 
routingMode, boolean specifyAsNumber) {
+                                         NetworkOffering.RoutingMode 
routingMode, boolean specifyAsNumber, boolean conserveMode) {
 
         if (!Ipv6Service.Ipv6OfferingCreationEnabled.value() && 
!(internetProtocol == null || 
NetUtils.InternetProtocol.IPv4.equals(internetProtocol))) {
             throw new 
InvalidParameterValueException(String.format("Configuration %s needs to be 
enabled for creating IPv6 supported VPC offering", 
Ipv6Service.Ipv6OfferingCreationEnabled.key()));
@@ -727,7 +728,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
         final boolean offersRegionLevelVPC = 
isVpcOfferingForRegionLevelVpc(serviceCapabilityList);
         final boolean redundantRouter = 
isVpcOfferingRedundantRouter(serviceCapabilityList, redundantRouterService);
         final VpcOfferingVO offering = createVpcOffering(name, displayText, 
svcProviderMap, false, state, serviceOfferingId, supportsDistributedRouter, 
offersRegionLevelVPC,
-                redundantRouter, networkMode, routingMode, specifyAsNumber);
+                redundantRouter, networkMode, routingMode, specifyAsNumber, 
conserveMode);
 
         if (offering != null) {
             List<VpcOfferingDetailsVO> detailsVO = new ArrayList<>();
@@ -755,7 +756,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
     @DB
     protected VpcOfferingVO createVpcOffering(final String name, final String 
displayText, final Map<Service, Set<Provider>> svcProviderMap,
                                               final boolean isDefault, final 
State state, final Long serviceOfferingId, final boolean 
supportsDistributedRouter, final boolean offersRegionLevelVPC,
-                                              final boolean redundantRouter, 
NetworkOffering.NetworkMode networkMode, NetworkOffering.RoutingMode 
routingMode, boolean specifyAsNumber) {
+                                              final boolean redundantRouter, 
NetworkOffering.NetworkMode networkMode, NetworkOffering.RoutingMode 
routingMode, boolean specifyAsNumber, boolean conserveMode) {
 
         return Transaction.execute(new TransactionCallback<VpcOfferingVO>() {
             @Override
@@ -771,6 +772,7 @@ public class VpcManagerImpl extends ManagerBase implements 
VpcManager, VpcProvis
                 if (Objects.nonNull(routingMode)) {
                     offering.setRoutingMode(routingMode);
                 }
+                offering.setConserveMode(conserveMode);
 
                 logger.debug("Adding vpc offering " + offering);
                 offering = _vpcOffDao.persist(offering);
@@ -2954,6 +2956,20 @@ public class VpcManagerImpl extends ManagerBase 
implements VpcManager, VpcProvis
         return true;
     }
 
+    protected boolean isNetworkOnVpc(Network network) {
+        return network.getVpcId() != null;
+    }
+
+    @Override
+    public boolean isNetworkOnVpcEnabledConserveMode(Network newRuleNetwork) {
+        if (isNetworkOnVpc(newRuleNetwork)) {
+            Vpc vpc = getActiveVpc(newRuleNetwork.getVpcId());
+            VpcOfferingVO vpcOffering = vpc != null ? 
_vpcOffDao.findById(vpc.getVpcOfferingId()) : null;
+            return vpcOffering != null && vpcOffering.isConserveMode();
+        }
+        return false;
+    }
+
     protected boolean applyStaticRoutes(final List<StaticRouteVO> routes, 
final Account caller, final boolean updateRoutesInDB) throws 
ResourceUnavailableException {
         final boolean success = true;
         final List<StaticRouteProfile> staticRouteProfiles = 
getVpcStaticRoutes(routes);
diff --git 
a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java 
b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
index 1b864bd695f..215a0e784bc 100644
--- a/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/as/AutoScaleManagerImplTest.java
@@ -1510,13 +1510,13 @@ public class AutoScaleManagerImplTest {
             
when(lbVmMapDao.listByLoadBalancerId(loadBalancerId)).thenReturn(Arrays.asList(loadBalancerVMMapMock));
             
when(loadBalancerVMMapMock.getInstanceId()).thenReturn(virtualMachineId + 1);
 
-            when(loadBalancingRulesService.assignToLoadBalancer(anyLong(), 
any(), any(), eq(true))).thenReturn(true);
+            when(loadBalancingRulesService.assignToLoadBalancer(anyLong(), 
any(), any(), any(), eq(true))).thenReturn(true);
             Mockito.doReturn(new Pair<UserVmVO, 
Map<VirtualMachineProfile.Param, Object>>(userVmMock, 
null)).when(userVmMgr).startVirtualMachine(virtualMachineId, null, new 
HashMap<>(), null);
 
             autoScaleManagerImplSpy.doScaleUp(vmGroupId, 1);
 
             Mockito.verify(autoScaleManagerImplSpy).createNewVM(asVmGroupMock);
-            
Mockito.verify(loadBalancingRulesService).assignToLoadBalancer(anyLong(), 
any(), any(), eq(true));
+            
Mockito.verify(loadBalancingRulesService).assignToLoadBalancer(anyLong(), 
any(), any(), any(), eq(true));
             Mockito.verify(userVmMgr).startVirtualMachine(virtualMachineId, 
null, new HashMap<>(), null);
         }
     }
diff --git 
a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java 
b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java
index f94fd0c0c3c..bacef85479a 100644
--- a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java
+++ b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java
@@ -24,12 +24,15 @@ import com.cloud.network.Network;
 import com.cloud.network.NetworkModel;
 import com.cloud.network.NetworkRuleApplier;
 import com.cloud.network.dao.FirewallRulesDao;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
 import com.cloud.network.element.FirewallServiceProvider;
 import com.cloud.network.element.VirtualRouterElement;
 import com.cloud.network.element.VpcVirtualRouterElement;
 import com.cloud.network.rules.FirewallRule;
 import com.cloud.network.rules.FirewallRule.Purpose;
 import com.cloud.network.rules.FirewallRuleVO;
+import com.cloud.network.vpc.Vpc;
 import com.cloud.network.vpc.VpcManager;
 import com.cloud.user.AccountManager;
 import com.cloud.user.DomainManager;
@@ -43,6 +46,7 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -76,6 +80,8 @@ public class FirewallManagerTest {
     IpAddressManager _ipAddrMgr;
     @Mock
     FirewallRulesDao _firewallDao;
+    @Mock
+    NetworkDao _networkDao;
 
     @Spy
     @InjectMocks
@@ -163,50 +169,98 @@ public class FirewallManagerTest {
         }
     }
 
-    @Test
-    public void testDetectRulesConflict() {
-        List<FirewallRuleVO> ruleList = new ArrayList<FirewallRuleVO>();
-        FirewallRuleVO rule1 = spy(new FirewallRuleVO("rule1", 3, 500, "UDP", 
1, 2, 1, Purpose.Vpn, null, null, null, null));
-        FirewallRuleVO rule2 = spy(new FirewallRuleVO("rule2", 3, 1701, "UDP", 
1, 2, 1, Purpose.Vpn, null, null, null, null));
-        FirewallRuleVO rule3 = spy(new FirewallRuleVO("rule3", 3, 4500, "UDP", 
1, 2, 1, Purpose.Vpn, null, null, null, null));
+    private List<FirewallRuleVO> createExistingFirewallListRulesList(long 
existingNetworkId) {
+        List<FirewallRuleVO> ruleList = new ArrayList<>();
+        FirewallRuleVO rule1 = spy(new FirewallRuleVO("rule1", 3, 500, "UDP", 
existingNetworkId, 2, 1, Purpose.Vpn, null, null, null, null));
+        FirewallRuleVO rule2 = spy(new FirewallRuleVO("rule2", 3, 1701, "UDP", 
existingNetworkId, 2, 1, Purpose.Vpn, null, null, null, null));
+        FirewallRuleVO rule3 = spy(new FirewallRuleVO("rule3", 3, 4500, "UDP", 
existingNetworkId, 2, 1, Purpose.Vpn, null, null, null, null));
 
         List<String> sString = Arrays.asList("10.1.1.1/24","192.168.1.1/24");
         List<String> dString1 = Arrays.asList("10.1.1.1/25");
-        List<String> dString2 = Arrays.asList("10.1.1.128/25");
 
-        FirewallRuleVO rule4 = spy(new FirewallRuleVO("rule4", 3L, 10, 20, 
"TCP", 1, 2, 1, Purpose.Firewall, sString, dString1, null, null,
+        FirewallRuleVO rule4 = spy(new FirewallRuleVO("rule4", 3L, 10, 20, 
"TCP", existingNetworkId, 2, 1, Purpose.Firewall, sString, dString1, null, null,
                 null, FirewallRule.TrafficType.Egress));
 
+        when(rule1.getId()).thenReturn(1L);
+        when(rule2.getId()).thenReturn(2L);
+        when(rule3.getId()).thenReturn(3L);
+        when(rule4.getId()).thenReturn(4L);
+
         ruleList.add(rule1);
         ruleList.add(rule2);
         ruleList.add(rule3);
         ruleList.add(rule4);
 
-        FirewallManagerImpl firewallMgr = (FirewallManagerImpl)_firewallMgr;
+        return ruleList;
+    }
 
-        
when(firewallMgr._firewallDao.listByIpAndPurposeAndNotRevoked(3,null)).thenReturn(ruleList);
-        when(rule1.getId()).thenReturn(1L);
-        when(rule2.getId()).thenReturn(2L);
-        when(rule3.getId()).thenReturn(3L);
-        when(rule4.getId()).thenReturn(4L);
+    private List<FirewallRule> createNewRuleList(long newNetworkId) {
+        List<String> sString = Arrays.asList("10.1.1.1/24","192.168.1.1/24");
+        List<String> dString2 = Arrays.asList("10.1.1.128/25");
 
-        FirewallRule newRule1 = new FirewallRuleVO("newRule1", 3, 500, "TCP", 
1, 2, 1, Purpose.PortForwarding, null, null, null, null);
-        FirewallRule newRule2 = new FirewallRuleVO("newRule2", 3, 1701, "TCP", 
1, 2, 1, Purpose.PortForwarding, null, null, null, null);
-        FirewallRule newRule3 = new FirewallRuleVO("newRule3", 3, 4500, "TCP", 
1, 2, 1, Purpose.PortForwarding, null, null, null, null);
-        FirewallRule newRule4 = new FirewallRuleVO("newRule4", 3L, 15, 25, 
"TCP", 1, 2, 1, Purpose.Firewall, sString, dString2, null, null,
+        FirewallRule newRule1 = new FirewallRuleVO("newRule1", 3, 500, "TCP", 
newNetworkId, 2, 1, Purpose.PortForwarding, null, null, null, null);
+        FirewallRule newRule2 = new FirewallRuleVO("newRule2", 3, 1701, "TCP", 
newNetworkId, 2, 1, Purpose.PortForwarding, null, null, null, null);
+        FirewallRule newRule3 = new FirewallRuleVO("newRule3", 3, 4500, "TCP", 
newNetworkId, 2, 1, Purpose.PortForwarding, null, null, null, null);
+        FirewallRule newRule4 = new FirewallRuleVO("newRule4", 3L, 15, 25, 
"TCP", newNetworkId, 2, 1, Purpose.Firewall, sString, dString2, null, null,
                 null, FirewallRule.TrafficType.Egress);
+        return Arrays.asList(newRule1, newRule2, newRule3, newRule4);
+    }
+
+    @Test
+    public void testDetectRulesConflictIsolatedNetwork() {
+        List<FirewallRuleVO> ruleList = 
createExistingFirewallListRulesList(1L);
+        
when(_firewallMgr._firewallDao.listByIpAndPurposeAndNotRevoked(3,null)).thenReturn(ruleList);
+
+        List<FirewallRule> newRuleList = createNewRuleList(1L);
+
+        NetworkVO networkVO = Mockito.mock(NetworkVO.class);
+        when(_firewallMgr._networkDao.findById(1L)).thenReturn(networkVO);
+        when(networkVO.getVpcId()).thenReturn(null);
 
         try {
-            firewallMgr.detectRulesConflict(newRule1);
-            firewallMgr.detectRulesConflict(newRule2);
-            firewallMgr.detectRulesConflict(newRule3);
-            firewallMgr.detectRulesConflict(newRule4);
+            for (FirewallRule newRule : newRuleList) {
+                _firewallMgr.detectRulesConflict(newRule);
+            }
         }
         catch (NetworkRuleConflictException ex) {
             Assert.fail();
         }
     }
 
+    private void testDetectRulesConflictVpcBase(boolean vpcConserveMode) 
throws NetworkRuleConflictException {
+        long existingNetworkId = 1L;
+        long newNetworkId = 2L;
+        long vpcId = 10L;
+
+        List<FirewallRuleVO> ruleList = 
createExistingFirewallListRulesList(existingNetworkId);
+        
when(_firewallMgr._firewallDao.listByIpAndPurposeAndNotRevoked(3,null)).thenReturn(ruleList);
+
+        List<FirewallRule> newRuleList = createNewRuleList(newNetworkId);
+
+        NetworkVO newNetworkVO = Mockito.mock(NetworkVO.class);
+        Vpc vpc = Mockito.mock(Vpc.class);
+        when(_firewallMgr._networkDao.findById(2L)).thenReturn(newNetworkVO);
+        when(newNetworkVO.getVpcId()).thenReturn(vpcId);
+        when(_vpcMgr.getActiveVpc(Mockito.eq(vpcId))).thenReturn(vpc);
+        
when(_vpcMgr.isNetworkOnVpcEnabledConserveMode(Mockito.eq(newNetworkVO))).thenReturn(vpcConserveMode);
+
+        for (FirewallRule newRule : newRuleList) {
+            _firewallMgr.detectRulesConflict(newRule);
+        }
+    }
+
+    @Test
+    public void testDetectRulesConflictVpcConserveMode() throws 
NetworkRuleConflictException {
+        // When VPC conserve mode is enabled, rules can be created for 
multiple network tiers
+        testDetectRulesConflictVpcBase(true);
+    }
+
+    @Test(expected = NetworkRuleConflictException.class)
+    public void testDetectRulesConflictVpcConserveModeFalse() throws 
NetworkRuleConflictException {
+        // When VPC conserve mode is disabled, an exception should be thrown 
when attempting to create rules on different network tiers
+        testDetectRulesConflictVpcBase(false);
+    }
+
     @Test
     public void 
checkIfRulesHaveConflictingPortRangesTestOnlyOneRuleIsFirewallReturnsFalse()
     {
diff --git 
a/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java 
b/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java
index 6b7677221bb..1fc0517a727 100644
--- a/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java
+++ b/server/src/test/java/com/cloud/network/lb/AssignLoadBalancerTest.java
@@ -32,6 +32,7 @@ import com.cloud.network.dao.LoadBalancerVO;
 import com.cloud.network.dao.NetworkDao;
 import com.cloud.network.rules.FirewallRule;
 import com.cloud.network.rules.RulesManagerImpl;
+import com.cloud.network.vpc.VpcManager;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
@@ -146,7 +147,7 @@ public class AssignLoadBalancerTest {
         
when(lbdao.findById(anyLong())).thenReturn(Mockito.mock(LoadBalancerVO.class));
         
when(autoScaleVmGroupDao.isAutoScaleLoadBalancer(anyLong())).thenReturn(Boolean.FALSE);
 
-        _lbMgr.assignToLoadBalancer(1L, null, emptyMap, false);
+        _lbMgr.assignToLoadBalancer(1L, null, emptyMap, null, false);
 
     }
 
@@ -171,6 +172,7 @@ public class AssignLoadBalancerTest {
         NetworkDao networkDao = Mockito.mock(NetworkDao.class);
         AccountManager accountMgr = Mockito.mock(AccountManager.class);
         AutoScaleVmGroupDao autoScaleVmGroupDao = 
Mockito.mock(AutoScaleVmGroupDao.class);
+        VpcManager vpcMgr = Mockito.mock(VpcManager.class);
 
         _lbMgr._lbDao = lbDao;
         _lbMgr._lb2VmMapDao = lb2VmMapDao;
@@ -182,6 +184,7 @@ public class AssignLoadBalancerTest {
         _lbMgr._rulesMgr = _rulesMgr;
         _lbMgr._networkModel = _networkModel;
         _lbMgr._autoScaleVmGroupDao = autoScaleVmGroupDao;
+        _lbMgr._vpcMgr = vpcMgr;
 
         
when(lbDao.findById(anyLong())).thenReturn(Mockito.mock(LoadBalancerVO.class));
         when(userVmDao.findById(anyLong())).thenReturn(vm);
@@ -189,8 +192,9 @@ public class AssignLoadBalancerTest {
         
when(accountDao.findById(anyLong())).thenReturn(Mockito.mock(AccountVO.class));
         Mockito.doNothing().when(accountMgr).checkAccess(any(Account.class), 
any(SecurityChecker.AccessType.class), any(Boolean.class), any(Network.class));
         
when(autoScaleVmGroupDao.isAutoScaleLoadBalancer(anyLong())).thenReturn(Boolean.FALSE);
+        
when(vpcMgr.isNetworkOnVpcEnabledConserveMode(any())).thenReturn(false);
 
-        _lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, false);
+        _lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, null, false);
     }
 
 
@@ -218,6 +222,7 @@ public class AssignLoadBalancerTest {
         AccountManager accountMgr = Mockito.mock(AccountManager.class);
         NicSecondaryIpDao nicSecIpDao =  Mockito.mock(NicSecondaryIpDao.class);
         AutoScaleVmGroupDao autoScaleVmGroupDao = 
Mockito.mock(AutoScaleVmGroupDao.class);
+        VpcManager vpcMgr = Mockito.mock(VpcManager.class);
 
         _lbMgr._lbDao = lbDao;
         _lbMgr._lb2VmMapDao = lb2VmMapDao;
@@ -230,14 +235,16 @@ public class AssignLoadBalancerTest {
         _lbMgr._rulesMgr = _rulesMgr;
         _lbMgr._networkModel = _networkModel;
         _lbMgr._autoScaleVmGroupDao = autoScaleVmGroupDao;
+        _lbMgr._vpcMgr = vpcMgr;
 
         when(lbDao.findById(anyLong())).thenReturn(lbVO);
         when(userVmDao.findById(anyLong())).thenReturn(vm);
         when(lb2VmMapDao.listByLoadBalancerId(anyLong(), 
anyBoolean())).thenReturn(_lbvmMapList);
         when (nicSecIpDao.findByIp4AddressAndNicId(anyString(), 
anyLong())).thenReturn(null);
         
when(autoScaleVmGroupDao.isAutoScaleLoadBalancer(anyLong())).thenReturn(Boolean.FALSE);
+        
when(vpcMgr.isNetworkOnVpcEnabledConserveMode(any())).thenReturn(false);
 
-        _lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, false);
+        _lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, null, false);
     }
 
 
@@ -267,6 +274,7 @@ public class AssignLoadBalancerTest {
         NicSecondaryIpDao nicSecIpDao =  Mockito.mock(NicSecondaryIpDao.class);
         LoadBalancerVMMapVO lbVmMapVO = new LoadBalancerVMMapVO(1L, 1L, 
"10.1.1.175", false);
         AutoScaleVmGroupDao autoScaleVmGroupDao = 
Mockito.mock(AutoScaleVmGroupDao.class);
+        VpcManager vpcMgr = Mockito.mock(VpcManager.class);
 
         _lbMgr._lbDao = lbDao;
         _lbMgr._lb2VmMapDao = lb2VmMapDao;
@@ -280,14 +288,16 @@ public class AssignLoadBalancerTest {
         _lbMgr._rulesMgr = _rulesMgr;
         _lbMgr._networkModel = _networkModel;
         _lbMgr._autoScaleVmGroupDao = autoScaleVmGroupDao;
+        _lbMgr._vpcMgr = vpcMgr;
 
         when(lbDao.findById(anyLong())).thenReturn(lbVO);
         when(userVmDao.findById(anyLong())).thenReturn(vm);
         when(lb2VmMapDao.listByLoadBalancerId(anyLong(), 
anyBoolean())).thenReturn(_lbvmMapList);
         when (nicSecIpDao.findByIp4AddressAndNicId(anyString(), 
anyLong())).thenReturn(null);
         
when(autoScaleVmGroupDao.isAutoScaleLoadBalancer(anyLong())).thenReturn(Boolean.FALSE);
+        
when(vpcMgr.isNetworkOnVpcEnabledConserveMode(any())).thenReturn(false);
 
-        _lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, false);
+        _lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, null, false);
     }
 
     @After
diff --git 
a/server/src/test/java/com/cloud/network/lb/LoadBalancingRulesManagerImplTest.java
 
b/server/src/test/java/com/cloud/network/lb/LoadBalancingRulesManagerImplTest.java
index 78655ba9a05..cfa0e2edaae 100644
--- 
a/server/src/test/java/com/cloud/network/lb/LoadBalancingRulesManagerImplTest.java
+++ 
b/server/src/test/java/com/cloud/network/lb/LoadBalancingRulesManagerImplTest.java
@@ -27,15 +27,18 @@ import com.cloud.network.dao.LoadBalancerVO;
 import com.cloud.network.dao.NetworkDao;
 import com.cloud.network.dao.NetworkVO;
 import com.cloud.network.dao.SslCertVO;
+import com.cloud.network.vpc.VpcManager;
 import com.cloud.offerings.dao.NetworkOfferingServiceMapDao;
 import com.cloud.user.Account;
 import com.cloud.user.AccountManager;
 import com.cloud.user.AccountVO;
 import com.cloud.user.User;
 import com.cloud.user.UserVO;
+import com.cloud.uservm.UserVm;
 import com.cloud.utils.db.EntityManager;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.net.NetUtils;
+import com.cloud.vm.Nic;
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ServerApiException;
@@ -54,7 +57,10 @@ import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import static org.mockito.ArgumentMatchers.anyLong;
@@ -89,6 +95,9 @@ public class LoadBalancingRulesManagerImplTest{
     @Mock
     NetworkOfferingServiceMapDao _networkOfferingServiceDao;
 
+    @Mock
+    VpcManager vpcManager;
+
     @Spy
     @InjectMocks
     LoadBalancingRulesManagerImpl lbr = new LoadBalancingRulesManagerImpl();
@@ -308,4 +317,47 @@ public class LoadBalancingRulesManagerImplTest{
         Mockito.verify(loadBalancerMock, 
times(1)).setLbProtocol(NetUtils.TCP_PROTO);
         Mockito.verify(loadBalancerMock, 
times(1)).setLbProtocol(NetUtils.SSL_PROTO);
     }
+
+    @Test
+    public void testGetVmNicInLoadBalancerDefaultCase() {
+        UserVm userVm = Mockito.mock(UserVm.class);
+        LoadBalancerVO loadBalancer = Mockito.mock(LoadBalancerVO.class);
+        Network loadBalancerNetwork = Mockito.mock(Network.class);
+        Account owner = Mockito.mock(Account.class);
+
+        
when(vpcManager.isNetworkOnVpcEnabledConserveMode(Mockito.eq(loadBalancerNetwork))).thenReturn(false);
+
+        when(loadBalancer.getNetworkId()).thenReturn(networkId);
+        Nic nic = Mockito.mock(Nic.class);
+        when(nic.getNetworkId()).thenReturn(networkId);
+        List<? extends Nic> nics = Collections.singletonList(nic);
+        Mockito.doReturn(nics).when(_networkModel).getNics(anyLong());
+        Nic nicInLb = lbr.getVmNicInLoadBalancer(userVm, loadBalancer, 
loadBalancerNetwork, null, owner);
+        Assert.assertEquals(nic, nicInLb);
+    }
+
+    @Test
+    public void testGetVmNicInLoadBalancerVPCConserveMode() {
+        long vmId = 30L;
+        UserVm userVm = Mockito.mock(UserVm.class);
+        when(userVm.getId()).thenReturn(vmId);
+        LoadBalancerVO loadBalancer = Mockito.mock(LoadBalancerVO.class);
+        Network loadBalancerNetwork = Mockito.mock(Network.class);
+        Account owner = Mockito.mock(Account.class);
+
+        long networkTier2Id = 20L;
+        NetworkVO networkTier2 = Mockito.mock(NetworkVO.class);
+        Map<Long, Long> vmIdNetworkIdMap = new HashMap<>();
+        vmIdNetworkIdMap.put(vmId, networkTier2Id);
+
+        
when(vpcManager.isNetworkOnVpcEnabledConserveMode(Mockito.eq(loadBalancerNetwork))).thenReturn(true);
+        when(_networkDao.findById(networkTier2Id)).thenReturn(networkTier2);
+        when(networkTier2.getVpcId()).thenReturn(10L);
+        when(loadBalancerNetwork.getVpcId()).thenReturn(10L);
+        Nic nic = Mockito.mock(Nic.class);
+        when(_networkModel.getNicInNetwork(Mockito.eq(vmId), 
Mockito.eq(networkTier2Id))).thenReturn(nic);
+
+        Nic nicInLb = lbr.getVmNicInLoadBalancer(userVm, loadBalancer, 
loadBalancerNetwork, vmIdNetworkIdMap, owner);
+        Assert.assertEquals(nic, nicInLb);
+    }
 }
diff --git a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java 
b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java
index 4f92c60e25a..ff34d72c218 100644
--- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java
+++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java
@@ -581,4 +581,27 @@ public class VpcManagerImplTest {
         Assert.assertThrows(InvalidParameterValueException.class, () -> 
manager.validateVpcPrivateGatewayAclId(vpcId, differentVpcAclId));
     }
 
+    @Test
+    public void testIsNetworkOnVpcEnabledConserveModeIsolatedNetwork() {
+        Network network = mock(Network.class);
+        Mockito.when(network.getVpcId()).thenReturn(null);
+        Assert.assertFalse(manager.isNetworkOnVpcEnabledConserveMode(network));
+    }
+
+    @Test
+    public void testIsNetworkOnVpcEnabledConserveModeVpcNetworkConserveMode() {
+        Network network = mock(Network.class);
+        Vpc vpc = mock(Vpc.class);
+        VpcOfferingVO vpcOffering = mock(VpcOfferingVO.class);
+        long vpcId = 10L;
+        long vpcOfferingId = 11L;
+
+        Mockito.when(network.getVpcId()).thenReturn(vpcId);
+        
Mockito.when(vpcDao.getActiveVpcById(Mockito.eq(vpcId))).thenReturn(vpc);
+        Mockito.when(vpc.getVpcOfferingId()).thenReturn(vpcOfferingId);
+        
Mockito.when(vpcOfferingDao.findById(Mockito.eq(vpcOfferingId))).thenReturn(vpcOffering);
+        Mockito.when(vpcOffering.isConserveMode()).thenReturn(true);
+        Assert.assertTrue(manager.isNetworkOnVpcEnabledConserveMode(network));
+    }
+
 }
diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java 
b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
index 54d8d67b6f8..de768388b44 100644
--- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
@@ -1148,4 +1148,9 @@ public class MockNetworkManagerImpl extends ManagerBase 
implements NetworkOrches
     public String getNicVlanValueForExternalVm(NicTO nic) {
         return null;
     }
+
+    @Override
+    public Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, 
Long networkId) {
+        return null;
+    }
 }
diff --git a/test/integration/smoke/test_domain_vpc_offerings.py 
b/test/integration/smoke/test_domain_vpc_offerings.py
index f3d31b2bf7e..9570d35c618 100644
--- a/test/integration/smoke/test_domain_vpc_offerings.py
+++ b/test/integration/smoke/test_domain_vpc_offerings.py
@@ -28,9 +28,16 @@ from marvin.lib.utils import (isAlmostEqual,
 from marvin.lib.base import (Domain,
                              VpcOffering,
                              Account,
-                             VPC)
+                             VPC,
+                             NetworkOffering,
+                             Network,
+                             VirtualMachine,
+                             ServiceOffering,
+                             PublicIPAddress,
+                             NATRule)
 from marvin.lib.common import (get_domain,
-                               get_zone)
+                               get_zone,
+                               get_test_template)
 from nose.plugins.attrib import attr
 
 import time
@@ -222,6 +229,7 @@ class TestDomainsVpcOfferings(cloudstackTestCase):
         cls.apiclient = testClient.getApiClient()
         cls.localservices = Services().services
         cls.services = testClient.getParsedTestDataConfig()
+        cls.hypervisor = cls.testClient.getHypervisorInfo()
         # Create domains
         cls.domain_1 = Domain.create(
             cls.apiclient,
@@ -402,3 +410,158 @@ class TestDomainsVpcOfferings(cloudstackTestCase):
         self.debug("Vpc created for first child subdomain %s" % 
self.valid_account_3.domainid)
 
         return
+
+    @attr(
+        tags=[
+            "advanced",
+            "eip",
+            "sg",
+            "advancedns",
+            "smoke"],
+        required_hardware="false")
+    def test_04_validate_vpc_offering_conserve_mode_disabled(self):
+        """Test to create and validate vpc with conserve mode disabled for an 
existing domain specified vpc offering"""
+
+        # Validate the following:
+        # 1. Create Vpc for user in domain for which offering is specified
+        # 2. Validate that conserve mode is disabled for the vpc (cannot reuse 
ip address on multiple VPC tiers)
+
+        template = get_test_template(
+            self.apiclient,
+            self.zone.id,
+            self.hypervisor)
+        if template == FAILED:
+            assert False, "get_test_template() failed to return template"
+
+        valid_account_1 = Account.create(
+            self.apiclient,
+            self.services["account"],
+            domainid=self.domain_1.id
+        )
+        self.cleanup.append(valid_account_1)
+
+        service_offering = ServiceOffering.create(
+            self.apiclient,
+            self.services["service_offerings"]["tiny"]
+        )
+        self.cleanup.append(service_offering)
+
+        self.services["vpc"]["cidr"] = "10.10.20.0/24"
+        vpc = VPC.create(
+            apiclient=self.apiclient,
+            services=self.services["vpc"],
+            account=valid_account_1.name,
+            domainid=valid_account_1.domainid,
+            zoneid=self.zone.id,
+            vpcofferingid=self.vpc_offering.id
+        )
+        self.debug("Vpc created for subdomain %s" % valid_account_1.domainid)
+
+        self.services["network_offering"]["supportedservices"] = 
'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding'
+        self.services["network_offering"]["serviceProviderList"] = {
+            "Vpn": 'VpcVirtualRouter',
+            "Dhcp": 'VpcVirtualRouter',
+            "Dns": 'VpcVirtualRouter',
+            "SourceNat": 'VpcVirtualRouter',
+            "Lb": 'VpcVirtualRouter',
+            "UserData": 'VpcVirtualRouter',
+            "StaticNat": 'VpcVirtualRouter',
+            "NetworkACL": 'VpcVirtualRouter',
+            "PortForwarding": 'VpcVirtualRouter'
+        }
+        network_offering = NetworkOffering.create(
+            self.apiclient,
+            self.services["network_offering"]
+        )
+        network_offering.update(self.apiclient, state="Enabled")
+        self.cleanup.append(network_offering)
+
+        gateway_tier1 = "10.10.20.1"
+        netmask_tiers = "255.255.255.240"
+
+        self.services["network_offering"]["name"] = "tier1-" + vpc.id
+        self.services["network_offering"]["displayname"] = "tier1-" + vpc.id
+        tier1 = Network.create(
+            self.apiclient,
+            services=self.services["network_offering"],
+            accountid=valid_account_1.name,
+            domainid=valid_account_1.domainid,
+            networkofferingid=network_offering.id,
+            zoneid=self.zone.id,
+            vpcid=vpc.id,
+            gateway=gateway_tier1,
+            netmask=netmask_tiers,
+        )
+
+        gateway_tier2 = "10.10.20.17"
+        self.services["network_offering"]["name"] = "tier2-" + vpc.id
+        self.services["network_offering"]["displayname"] = "tier2-" + vpc.id
+        tier2 = Network.create(
+            self.apiclient,
+            services=self.services["network_offering"],
+            accountid=valid_account_1.name,
+            domainid=valid_account_1.domainid,
+            networkofferingid=network_offering.id,
+            zoneid=self.zone.id,
+            vpcid=vpc.id,
+            gateway=gateway_tier2,
+            netmask=netmask_tiers,
+        )
+
+        self.services["virtual_machine"]["displayname"] = "vm1" + vpc.id
+        vm1 = VirtualMachine.create(
+            self.apiclient,
+            services=self.services["virtual_machine"],
+            templateid=template.id,
+            zoneid=self.zone.id,
+            accountid=valid_account_1.name,
+            domainid=valid_account_1.domainid,
+            serviceofferingid=service_offering.id,
+            networkids=[tier1.id],
+        )
+
+        self.services["virtual_machine"]["displayname"] = "vm2" + vpc.id
+        vm2 = VirtualMachine.create(
+            self.apiclient,
+            services=self.services["virtual_machine"],
+            templateid=template.id,
+            zoneid=self.zone.id,
+            accountid=valid_account_1.name,
+            domainid=valid_account_1.domainid,
+            serviceofferingid=service_offering.id,
+            networkids=[tier2.id],
+        )
+
+        public_ip = PublicIPAddress.create(
+            self.apiclient,
+            zoneid=self.zone.id,
+            accountid=valid_account_1.name,
+            domainid=valid_account_1.domainid,
+            vpcid=vpc.id,
+        )
+
+        nat_rule = NATRule.create(
+            self.apiclient,
+            vm1,
+            self.services["natrule"],
+            ipaddressid=public_ip.ipaddress.id,
+            vpcid=vpc.id,
+            networkid=tier1.id,
+        )
+
+        self.services["natrule"]["privateport"] = 80
+        self.services["natrule"]["publicport"] = 80
+        try:
+            NATRule.create(
+                self.apiclient,
+                vm2,
+                self.services["natrule"],
+                ipaddressid=public_ip.ipaddress.id,
+                vpcid=vpc.id,
+                networkid=tier2.id,
+            )
+            self.fail(
+                "Expected cross-tier rule creation to fail with 
conserveMode=False, but succeeded"
+            )
+        except CloudstackAPIException as e:
+            self.debug("Expected cross-tier rule creation to failure with 
conserveMode=False")
diff --git a/test/integration/smoke/test_vpc_conserve_mode.py 
b/test/integration/smoke/test_vpc_conserve_mode.py
new file mode 100644
index 00000000000..a56953db787
--- /dev/null
+++ b/test/integration/smoke/test_vpc_conserve_mode.py
@@ -0,0 +1,314 @@
+# 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.
+
+"""Tests for VPC Conserve Mode (since 4.23.0)
+
+Conserve mode allows public IP services (LB, Port Forwarding, Static NAT) to be
+shared across multiple VPC tiers using the same public IP address.
+
+When conserve mode is ON:
+  - A single public IP can have rules targeting VMs in different VPC tiers
+  - FirewallManagerImpl skips the cross-network conflict check for that VPC
+
+When conserve mode is OFF (default before 4.23.0):
+  - Rules on a given public IP must all belong to the same VPC tier (network)
+  - Attempting to create a rule on a different tier than an existing rule 
raises
+    a NetworkRuleConflictException
+"""
+
+from marvin.cloudstackException import CloudstackAPIException
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.codes import FAILED
+from marvin.lib.base import (
+    Account,
+    LoadBalancerRule,
+    NATRule,
+    Network,
+    NetworkOffering,
+    PublicIPAddress,
+    ServiceOffering,
+    VirtualMachine,
+    VPC,
+    VpcOffering,
+)
+from marvin.lib.common import (
+    get_domain,
+    get_test_template,
+    get_zone,
+    list_publicIP
+)
+from marvin.lib.utils import cleanup_resources
+from nose.plugins.attrib import attr
+import logging
+
+class TestVPCConserveModeRules(cloudstackTestCase):
+    """Tests that conserve mode for VPC controls whether rules on the same 
public IP are allowed in multiple VPC tiers.
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(TestVPCConserveModeRules, 
cls).getClsTestClient()
+        cls.apiclient = cls.testClient.getApiClient()
+        cls.services = cls.testClient.getParsedTestDataConfig()
+        cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        cls.domain = get_domain(cls.apiclient)
+        cls.hypervisor = cls.testClient.getHypervisorInfo()
+        cls.logger = logging.getLogger("TestVPCConserveModeRules")
+        cls._cleanup = []
+
+        cls.account = Account.create(
+            cls.apiclient,
+            cls.services["account"],
+            admin=True,
+            domainid=cls.domain.id)
+        cls._cleanup.append(cls.account)
+
+        cls.template = get_test_template(
+            cls.apiclient,
+            cls.zone.id,
+            cls.hypervisor)
+        if cls.template == FAILED:
+            assert False, "get_test_template() failed to return template"
+
+        cls.service_offering = ServiceOffering.create(
+            cls.apiclient,
+            cls.services["service_offerings"]["tiny"]
+        )
+        cls._cleanup.append(cls.service_offering)
+
+        cls.services["vpc_offering"]["supportedservices"] = 
'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding'
+        cls.services["vpc_offering"]["conservemode"] = True
+        cls.vpc_offering_conserve_mode = VpcOffering.create(
+            cls.apiclient,
+            cls.services["vpc_offering"]
+        )
+        cls.vpc_offering_conserve_mode.update(cls.apiclient, state="Enabled")
+        cls._cleanup.append(cls.vpc_offering_conserve_mode)
+
+        cls.services["network_offering"]["supportedservices"] = 
'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding'
+        cls.services["network_offering"]["serviceProviderList"] = {
+            "Vpn": 'VpcVirtualRouter',
+            "Dhcp": 'VpcVirtualRouter',
+            "Dns": 'VpcVirtualRouter',
+            "SourceNat": 'VpcVirtualRouter',
+            "Lb": 'VpcVirtualRouter',
+            "UserData": 'VpcVirtualRouter',
+            "StaticNat": 'VpcVirtualRouter',
+            "NetworkACL": 'VpcVirtualRouter',
+            "PortForwarding": 'VpcVirtualRouter'
+        }
+        cls.network_offering = NetworkOffering.create(
+            cls.apiclient,
+            cls.services["network_offering"],
+            conservemode=True
+        )
+        cls.network_offering.update(cls.apiclient, state="Enabled")
+        cls._cleanup.append(cls.network_offering)
+
+        cls.services["vpc"]["cidr"] = "10.10.20.0/24"
+
+        cls.vpc = VPC.create(
+            cls.apiclient,
+            cls.services["vpc"],
+            vpcofferingid=cls.vpc_offering_conserve_mode.id,
+            zoneid=cls.zone.id,
+            account=cls.account.name,
+            domainid=cls.account.domainid,
+        )
+        cls._cleanup.append(cls.vpc)
+
+        gateway_tier1 = "10.10.20.1"
+        netmask_tiers = "255.255.255.240"
+
+        cls.services["network_offering"]["name"] = "tier1-" + cls.vpc.id
+        cls.services["network_offering"]["displayname"] = "tier1-" + cls.vpc.id
+        cls.tier1 = Network.create(
+            cls.apiclient,
+            services=cls.services["network_offering"],
+            accountid=cls.account.name,
+            domainid=cls.account.domainid,
+            networkofferingid=cls.network_offering.id,
+            zoneid=cls.zone.id,
+            vpcid=cls.vpc.id,
+            gateway=gateway_tier1,
+            netmask=netmask_tiers,
+        )
+        cls._cleanup.append(cls.tier1)
+
+        gateway_tier2 = "10.10.20.17"
+        cls.services["network_offering"]["name"] = "tier2-" + cls.vpc.id
+        cls.services["network_offering"]["displayname"] = "tier2-" + cls.vpc.id
+        cls.tier2 = Network.create(
+            cls.apiclient,
+            services=cls.services["network_offering"],
+            accountid=cls.account.name,
+            domainid=cls.account.domainid,
+            networkofferingid=cls.network_offering.id,
+            zoneid=cls.zone.id,
+            vpcid=cls.vpc.id,
+            gateway=gateway_tier2,
+            netmask=netmask_tiers,
+        )
+        cls._cleanup.append(cls.tier2)
+
+        cls.services["virtual_machine"]["displayname"] = "vm1" + cls.vpc.id
+        cls.vm1 = VirtualMachine.create(
+            cls.apiclient,
+            services=cls.services["virtual_machine"],
+            templateid=cls.template.id,
+            zoneid=cls.zone.id,
+            accountid=cls.account.name,
+            domainid=cls.account.domainid,
+            serviceofferingid=cls.service_offering.id,
+            networkids=[cls.tier1.id],
+        )
+        cls.services["virtual_machine"]["displayname"] = "vm2" + cls.vpc.id
+        cls.vm2 = VirtualMachine.create(
+            cls.apiclient,
+            services=cls.services["virtual_machine"],
+            templateid=cls.template.id,
+            zoneid=cls.zone.id,
+            accountid=cls.account.name,
+            domainid=cls.account.domainid,
+            serviceofferingid=cls.service_offering.id,
+            networkids=[cls.tier2.id],
+        )
+        cls._cleanup.append(cls.vm1)
+        cls._cleanup.append(cls.vm2)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestVPCConserveModeRules, cls).tearDownClass()
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.cleanup = []
+
+    def tearDown(self):
+        super(TestVPCConserveModeRules, self).tearDown()
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
+    def test_01_vpc_conserve_mode_cross_tier_rules_allowed(self):
+        """With conserveMode=True, LB rule on VPC Tier 1 and Port Forwarding 
rule on VPC Tier 2 can
+        share the same public IP without a NetworkRuleConflictException.
+        """
+
+        public_ip = PublicIPAddress.create(
+            self.apiclient,
+            zoneid=self.zone.id,
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            vpcid=self.vpc.id,
+        )
+
+        self.logger.debug(
+            "Creating LB rule on tier-1 (networkid=%s) using public IP %s",
+            self.tier1.id,
+            public_ip.ipaddress.ipaddress,
+        )
+        lb_rule_tier1 = LoadBalancerRule.create(
+            self.apiclient,
+            self.services["lbrule"],
+            ipaddressid=public_ip.ipaddress.id,
+            accountid=self.account.name,
+            vpcid=self.vpc.id,
+            networkid=self.tier1.id,
+            domainid=self.account.domainid,
+        )
+        self.assertIsNotNone(lb_rule_tier1, "LB rule creation on tier-1 
failed")
+        lb_rule_tier1.assign(self.apiclient, [self.vm1])
+
+        self.logger.debug(
+            "Creating Port Forwarding rule on tier-2 (networkid=%s) "
+            "using the same public IP %s – should succeed with conserve mode",
+            self.tier2.id,
+            public_ip.ipaddress.ipaddress,
+        )
+        try:
+            nat_rule = NATRule.create(
+                self.apiclient,
+                self.vm2,
+                self.services["natrule"],
+                ipaddressid=public_ip.ipaddress.id,
+                vpcid=self.vpc.id,
+                networkid=self.tier2.id,
+            )
+            self.assertIsNotNone(
+                nat_rule,
+                "Port Forwarding rule creation on tier-2 failed unexpectedly",
+            )
+        except CloudstackAPIException as e:
+            self.fail(
+                "Expected cross-tier Port Forwarding rule to succeed with "
+                "conserveMode=True, but got exception: %s" % e
+            )
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
+    def test_02_vpc_conserve_mode_reuse_source_nat_ip_address(self):
+        """With VPC conserve mode enabled, a NAT rule can be created on a VPC 
tier (conserve mode enabled)
+        with a source NAT IP address
+        """
+        source_nat_ip_resp = list_publicIP(
+            self.apiclient,
+            vpcid=self.vpc.id,
+            listall=True,
+            issourcenat=True
+        )
+
+        source_nat_ip = source_nat_ip_resp[0]
+
+        self.logger.debug(
+            "Creating Port Forwarding rule on tier-2 (networkid=%s) "
+            "using the source NAT public IP %s – should succeed with conserve 
mode",
+            self.tier1.id,
+            source_nat_ip.ipaddress,
+        )
+        try:
+            nat_rule = NATRule.create(
+                self.apiclient,
+                self.vm2,
+                self.services["natrule"],
+                ipaddressid=source_nat_ip.id,
+                vpcid=self.vpc.id,
+                networkid=self.tier2.id,
+            )
+            self.assertIsNotNone(
+                nat_rule,
+                "Port Forwarding rule creation on tier-2 failed unexpectedly",
+            )
+            self.logger.debug(
+                "Creating LB rule on tier-1 (networkid=%s) "
+                "using the source NAT public IP %s – should succeed with 
conserve mode",
+                self.tier1.id,
+                source_nat_ip.ipaddress,
+            )
+            lb_rule_tier1 = LoadBalancerRule.create(
+                self.apiclient,
+                self.services["lbrule"],
+                ipaddressid=source_nat_ip.id,
+                accountid=self.account.name,
+                vpcid=self.vpc.id,
+                networkid=self.tier2.id,
+                domainid=self.account.domainid,
+            )
+            self.assertIsNotNone(lb_rule_tier1, "LB rule creation on tier-2 
failed")
+            lb_rule_tier1.assign(self.apiclient, [self.vm2])
+        except CloudstackAPIException as e:
+            self.fail(
+                "Expected multiple rules on VPC Source NAT IP to succeed with "
+                "conserveMode=True, but got exception: %s" % e
+            )
diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py
index 825159a2e53..636c73209a3 100755
--- a/tools/marvin/marvin/lib/base.py
+++ b/tools/marvin/marvin/lib/base.py
@@ -5227,6 +5227,8 @@ class VpcOffering:
             cmd.networkmode = services["networkmode"]
         if "routingmode" in services:
             cmd.routingmode = services["routingmode"]
+        if "conservemode" in services:
+            cmd.conservemode = services["conservemode"]
         return VpcOffering(apiclient.createVPCOffering(cmd).__dict__)
 
     def update(self, apiclient, name=None, displaytext=None, state=None):
diff --git a/ui/src/config/section/offering.js 
b/ui/src/config/section/offering.js
index bc95772d6f7..436c0cf1b60 100644
--- a/ui/src/config/section/offering.js
+++ b/ui/src/config/section/offering.js
@@ -508,7 +508,7 @@ export default {
       searchFilters: ['name', 'zoneid', 'domainid'],
       resourceType: 'VpcOffering',
       columns: ['name', 'state', 'displaytext', 'domain', 'zone', 'order'],
-      details: ['name', 'id', 'displaytext', 'internetprotocol', 
'distributedvpcrouter', 'tags', 'routingmode', 'specifyasnumber', 'service', 
'fornsx', 'networkmode', 'domain', 'zone', 'created'],
+      details: ['name', 'id', 'displaytext', 'internetprotocol', 
'distributedvpcrouter', 'tags', 'routingmode', 'specifyasnumber', 'service', 
'fornsx', 'networkmode', 'conservemode', 'domain', 'zone', 'created'],
       related: [{
         name: 'vpc',
         title: 'label.vpc',
diff --git a/ui/src/views/network/LoadBalancing.vue 
b/ui/src/views/network/LoadBalancing.vue
index ad091b218a8..0b9ed7684a8 100644
--- a/ui/src/views/network/LoadBalancing.vue
+++ b/ui/src/views/network/LoadBalancing.vue
@@ -204,6 +204,11 @@
                   {{ instance.loadbalancerruleinstance.displayname }}
                 </router-link>
               </div>
+              <div v-if="this.vpcConserveMode">
+                <router-link :to="{ path: '/guestnetwork/' + 
instance.loadbalancerruleinstance.nic[0].networkid }">
+                  {{ instance.loadbalancerruleinstance.nic[0].networkname }}
+                </router-link>
+              </div>
               <div>{{ ip }}</div>
               <tooltip-button
                 :disabled='record.autoscalevmgroup'
@@ -487,10 +492,10 @@
     >
       <div @keyup.ctrl.enter="handleAddNewRule">
         <span
-          v-if="'vpcid' in resource && !('associatednetworkid' in resource)">
+          v-if="'vpcid' in resource && (!('associatednetworkid' in resource) 
|| vpcConserveMode)">
           <strong>{{ $t('label.select.tier') }} </strong>
           <a-select
-            v-focus="'vpcid' in resource && !('associatednetworkid' in 
resource)"
+            v-focus="'vpcid' in resource && (!('associatednetworkid' in 
resource) || vpcConserveMode)"
             v-model:value="selectedTier"
             @change="fetchVirtualMachines()"
             :placeholder="$t('label.select.tier')"
@@ -1022,7 +1027,8 @@ export default {
         urlpath: '/'
       },
       healthMonitorLoading: false,
-      isNetrisZone: false
+      isNetrisZone: false,
+      vpcConserveMode: false
     }
   },
   computed: {
@@ -1079,10 +1085,24 @@ export default {
       })
     },
     fetchData () {
+      this.fetchVpc()
       this.fetchListTiers()
       this.fetchLBRules()
       this.fetchZone()
     },
+    fetchVpc () {
+      if (!this.resource.vpcid) {
+        return
+      }
+      this.vpcConserveMode = false
+      getAPI('listVPCs', {
+        id: this.resource.vpcid
+      }).then(json => {
+        this.vpcConserveMode = 
json.listvpcsresponse?.vpc?.[0].vpcofferingconservemode || false
+      }).catch(error => {
+        this.$notifyError(error)
+      })
+    },
     fetchListTiers () {
       this.tiers.loading = true
 
@@ -1830,7 +1850,7 @@ export default {
 
       getAPI('listNics', {
         virtualmachineid: e.target.value,
-        networkid: ('vpcid' in this.resource && !('associatednetworkid' in 
this.resource)) ? this.selectedTier : this.resource.associatednetworkid
+        networkid: ('vpcid' in this.resource && (!('associatednetworkid' in 
this.resource) || this.vpcConserveMode)) ? this.selectedTier : 
this.resource.associatednetworkid
       }).then(response => {
         if (!response || !response.listnicsresponse || 
!response.listnicsresponse.nic[0]) return
         const newItem = []
@@ -1850,7 +1870,7 @@ export default {
       this.vmCount = 0
       this.vms = []
       this.addVmModalLoading = true
-      const networkId = ('vpcid' in this.resource && !('associatednetworkid' 
in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
+      const networkId = ('vpcid' in this.resource && (!('associatednetworkid' 
in this.resource) || this.vpcConserveMode)) ? this.selectedTier : 
this.resource.associatednetworkid
       if (!networkId) {
         this.addVmModalLoading = false
         return
@@ -1935,11 +1955,17 @@ export default {
           ip.forEach(i => {
             vmIDIpMap[`vmidipmap[${innerCount}].vmid`] = 
this.newRule.virtualmachineid[count]
             vmIDIpMap[`vmidipmap[${innerCount}].vmip`] = i
+            if (this.vpcConserveMode) {
+              vmIDIpMap[`vmidipmap[${innerCount}].vmnetworkid`] = 
this.selectedTier
+            }
             innerCount++
           })
         } else {
           vmIDIpMap[`vmidipmap[${innerCount}].vmid`] = 
this.newRule.virtualmachineid[count]
           vmIDIpMap[`vmidipmap[${innerCount}].vmip`] = ip
+          if (this.vpcConserveMode && ip != null) {
+            vmIDIpMap[`vmidipmap[${innerCount}].vmnetworkid`] = 
this.selectedTier
+          }
           innerCount++
         }
         if (this.newRule.virtualmachineid[count]) {
diff --git a/ui/src/views/network/PortForwarding.vue 
b/ui/src/views/network/PortForwarding.vue
index 8ab6559b12c..ffa89e5b581 100644
--- a/ui/src/views/network/PortForwarding.vue
+++ b/ui/src/views/network/PortForwarding.vue
@@ -117,6 +117,12 @@
         <template v-if="column.key === 'cidrlist'">
           <span style="white-space: pre-line"> {{ 
record.cidrlist?.replaceAll(",", "\n") }}</span>
         </template>
+        <template v-if="vpcConserveMode && column.key === 'networkid'">
+          <router-link
+            :to="{ path: '/guestnetwork/' + record.networkid }">
+            {{ record.networkname }}
+          </router-link>
+        </template>
         <template v-if="column.key === 'vm'">
           <div><desktop-outlined/>
             <router-link
@@ -216,10 +222,10 @@
       @cancel="closeModal">
       <div v-ctrl-enter="addRule">
         <span
-          v-if="'vpcid' in resource && !('associatednetworkid' in resource)">
+          v-if="'vpcid' in resource && (!('associatednetworkid' in resource) 
|| vpcConserveMode)">
           <strong>{{ $t('label.select.tier') }} </strong>
           <a-select
-            :v-focus="'vpcid' in resource && !('associatednetworkid' in 
resource)"
+            v-focus="'vpcid' in resource && (!('associatednetworkid' in 
resource) || vpcConserveMode)"
             v-model:value="selectedTier"
             @change="fetchVirtualMachines()"
             :placeholder="$t('label.select.tier')"
@@ -467,7 +473,8 @@ export default {
       vmPageSize: 10,
       vmCount: 0,
       searchQuery: null,
-      cidrlist: ''
+      cidrlist: '',
+      vpcConserveMode: false
     }
   },
   computed: {
@@ -479,7 +486,6 @@ export default {
     this.apiParams = this.$getApiParams('createPortForwardingRule')
   },
   created () {
-    console.log(this.resource)
     this.initForm()
     this.fetchData()
   },
@@ -504,13 +510,30 @@ export default {
       })
     },
     fetchData () {
+      this.fetchVpc()
       this.fetchListTiers()
       this.fetchPFRules()
     },
-    fetchListTiers () {
-      if ('vpcid' in this.resource && 'associatednetworkid' in this.resource) {
+    fetchVpc () {
+      if (!this.resource.vpcid) {
         return
       }
+      this.vpcConserveMode = false
+      getAPI('listVPCs', {
+        id: this.resource.vpcid
+      }).then(json => {
+        this.vpcConserveMode = 
json.listvpcsresponse?.vpc?.[0].vpcofferingconservemode || false
+        if (this.vpcConserveMode) {
+          this.columns.splice(this.columns.length - 1, 0, {
+            key: 'networkid',
+            title: this.$t('label.network')
+          })
+        }
+      }).catch(error => {
+        this.$notifyError(error)
+      })
+    },
+    fetchListTiers () {
       this.selectedTier = null
       this.tiers.loading = true
       getAPI('listNetworks', {
@@ -630,7 +653,7 @@ export default {
       if (this.loading) return
       this.loading = true
       this.addVmModalVisible = false
-      const networkId = ('vpcid' in this.resource && !('associatednetworkid' 
in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
+      const networkId = ('vpcid' in this.resource && (!('associatednetworkid' 
in this.resource) || this.vpcConserveMode)) ? this.selectedTier : 
this.resource.associatednetworkid
       postAPI('createPortForwardingRule', {
         ...this.newRule,
         ipaddressid: this.resource.id,
@@ -788,7 +811,7 @@ export default {
       this.newRule.virtualmachineid = e.target.value
       getAPI('listNics', {
         virtualmachineid: e.target.value,
-        networkId: ('vpcid' in this.resource && !('associatednetworkid' in 
this.resource)) ? this.selectedTier : this.resource.associatednetworkid
+        networkid: ('vpcid' in this.resource && (!('associatednetworkid' in 
this.resource) || this.vpcConserveMode)) ? this.selectedTier : 
this.resource.associatednetworkid
       }).then(response => {
         if (!response.listnicsresponse.nic || 
response.listnicsresponse.nic.length < 1) return
         const nic = response.listnicsresponse.nic[0]
@@ -799,7 +822,6 @@ export default {
         this.newRule.vmguestip = this.nics[0]
         this.addVmModalNicLoading = false
       }).catch(error => {
-        console.log(error)
         this.$notifyError(error)
         this.closeModal()
       })
@@ -808,7 +830,7 @@ export default {
       this.vmCount = 0
       this.vms = []
       this.addVmModalLoading = true
-      const networkId = ('vpcid' in this.resource && !('associatednetworkid' 
in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
+      const networkId = ('vpcid' in this.resource && (!('associatednetworkid' 
in this.resource) || this.vpcConserveMode)) ? this.selectedTier : 
this.resource.associatednetworkid
       if (!networkId) {
         this.addVmModalLoading = false
         return
diff --git a/ui/src/views/network/PublicIpResource.vue 
b/ui/src/views/network/PublicIpResource.vue
index 7c25e1c32ba..0540e7f292a 100644
--- a/ui/src/views/network/PublicIpResource.vue
+++ b/ui/src/views/network/PublicIpResource.vue
@@ -135,8 +135,10 @@ export default {
         return
       }
       if (this.resource && this.resource.vpcid) {
-        // VPC IPs with source nat have only VPN
-        if (this.resource.issourcenat) {
+        const vpc = await this.fetchVpc()
+
+        // VPC IPs with source nat have only VPN when VPC offering conserve 
mode = false
+        if (this.resource.issourcenat && vpc?.vpcofferingconservemode === 
false) {
           this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab 
=> tab.name === 'vpn'))
           return
         }
@@ -154,7 +156,12 @@ export default {
 
         const network = await this.fetchNetwork()
         if (network && network.networkofferingconservemode) {
-          this.tabs = tabs
+          // VPC IPs with source nat have only VPN when VPC offering conserve 
mode = false
+          if (this.resource.issourcenat && vpc?.vpcofferingconservemode === 
false) {
+            this.tabs = 
this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn'))
+          } else {
+            this.tabs = tabs
+          }
           return
         }
 
@@ -193,6 +200,21 @@ export default {
     fetchAction () {
       this.actions = this.$route.meta.actions || []
     },
+    fetchVpc () {
+      if (!this.resource.vpcid) {
+        return null
+      }
+      return new Promise((resolve, reject) => {
+        getAPI('listVPCs', {
+          id: this.resource.vpcid
+        }).then(json => {
+          const vpc = json.listvpcsresponse?.vpc?.[0] || null
+          resolve(vpc)
+        }).catch(e => {
+          reject(e)
+        })
+      })
+    },
     fetchNetwork () {
       if (!this.resource.associatednetworkid) {
         return null
diff --git a/ui/src/views/offering/AddVpcOffering.vue 
b/ui/src/views/offering/AddVpcOffering.vue
index 32aa3e8d358..17939d2b19e 100644
--- a/ui/src/views/offering/AddVpcOffering.vue
+++ b/ui/src/views/offering/AddVpcOffering.vue
@@ -194,6 +194,14 @@
             </a-select-option>
           </a-select>
         </a-form-item>
+        <a-form-item
+          name="conservemode"
+          ref="conservemode">
+          <template #label>
+            <tooltip-label :title="$t('label.conservemode')" 
:tooltip="apiParams.conservemode.description"/>
+          </template>
+          <a-switch v-model:checked="form.conservemode" />
+        </a-form-item>
         <a-form-item name="ispublic" ref="ispublic" 
:label="$t('label.ispublic')" v-if="isAdmin()">
           <a-switch v-model:checked="form.ispublic" />
         </a-form-item>
@@ -282,7 +290,6 @@ export default {
     return {
       selectedDomains: [],
       selectedZones: [],
-      isConserveMode: true,
       internetProtocolValue: 'ipv4',
       domains: [],
       domainLoading: false,
@@ -328,7 +335,8 @@ export default {
         description: 'Netris',
         enabled: true
       },
-      nsxSupportedServicesMap: {}
+      nsxSupportedServicesMap: {},
+      conservemode: false
     }
   },
   beforeCreate () {
@@ -719,6 +727,7 @@ export default {
           params.provider = 'Netris'
         }
         params.networkmode = values.networkmode
+        params.conservemode = values.conservemode
         if (!values.forVpc) {
           params.specifyasnumber = values.specifyasnumber
         }

Reply via email to