diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4edd448067ae..6957d3f54464 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 742206c7e3bf..53692f932a4e 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 @@ Network createPrivateNetwork(String networkName, String displayText, long physic 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 0bf06be15d87..b7fe3b26761c 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 @@ LoadBalancer createPublicLoadBalancerRule(String xId, String name, String descri /** * Assign a virtual machine or list of virtual machines, or Map of to a load balancer. */ - boolean assignToLoadBalancer(long lbRuleId, List vmIds, Map> vmIdIpMap, boolean isAutoScaleVM); + boolean assignToLoadBalancer(long lbRuleId, List vmIds, Map> vmIdIpMap, Map 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 17f49bb36521..f84602232159 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 enum State { 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 97b95339ecf3..fbcf4f08bcc0 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 @@ VpcOffering createVpcOffering(String name, String displayText, List supp Map serviceCapabilitystList, NetUtils.InternetProtocol internetProtocol, Long serviceOfferingId, String externalProvider, NetworkOffering.NetworkMode networkMode, List domainIds, List zoneIds, VpcOffering.State state, - NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber); + NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode); Pair,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 25ba233cf80e..2bc9b791184c 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 6b425bc10d21..dafa72dac0a7 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 String getRoutingMode() { 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 056807b9b535..2bc5fc2ee68b 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 @@ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements PortForwardingRule { - // /////////////////////////////////////////////////// // ////////////// API parameters ///////////////////// // /////////////////////////////////////////////////// @@ -278,13 +277,7 @@ public State getState() { @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 6d8d356cea4d..cc7cd2382b75 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.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 String getEventDescription() { } - public Map> getVmIdIpListMap() { - Map> vmIdIpsMap = new HashMap>(); + public Pair>, Map> getVmIdIpListMapAndVmIdNetworkMap() { + Map> vmIdIpsMap = new HashMap<>(); + Map vmIdNetworkMap = new HashMap<>(); if (vmIdIpMap != null && !vmIdIpMap.isEmpty()) { Collection idIpsCollection = vmIdIpMap.values(); Iterator iter = idIpsCollection.iterator(); @@ -125,6 +128,7 @@ public Map> getVmIdIpListMap() { HashMap idIpsMap = (HashMap)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 Map> getVmIdIpListMap() { ipsList = new ArrayList(); } 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> vmIdIpsMap = getVmIdIpListMap(); + Pair>, Map> mapsPair = getVmIdIpListMapAndVmIdNetworkMap(); + Map> vmIdIpsMap = mapsPair.first(); + Map 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 48097e51d992..aed56a369089 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 void setNetworkId(String networkId) { 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 a0516e660e48..2e821dae52de 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 String getRoutingMode() { 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 2648ba836785..acfabb113502 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 void setDisplayText(final String displayText) { 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 b1cad20b19ec..454cb10a2f2b 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 @@ List listAvailablePublicIps(final long dcId, PublicIpQuarantine updatePublicIpAddressInQuarantine(Long quarantineProcessId, Date endDate); void updateSourceNatIpAddress(IPAddressVO requestedIp, List 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 669456cbdcc2..d8011e9ade12 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 @@ 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 e7f41d079a74..792a3a6b397f 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 9320a37bc96e..b913468384e4 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 Boolean isSpecifyAsNumber() { 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 7923ef89ffde..209d79cd8e9d 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 @@ -98,3 +98,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 751d8f91a259..3669bb10122b 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 cf69234d19e0..8c7c0838f41b 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 @@ -660,7 +660,7 @@ protected void provisionLoadBalancerRule(final IpAddress publicIp, final Network 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 createFirewallRules(IpAddress publicIp, List 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 3df58470fc68..fc167b71c23d 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 @@ private LoadBalancer handleCreateLoadBalancerRuleWithLock(final CreateLoadBalanc 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 f360fab01124..8badb916eeda 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 @@ private VpcOffering locateVpcOffering() { } 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 51cf82b13b06..cf98df0da243 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 FirewallRuleResponse createPortForwardingRuleResponse(PortForwardingRule 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 VpcResponse createVpcResponse(ResponseView view, Vpc vpc) { 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 7ea4b7d5834f..e7fe07a18c78 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 VpcOfferingResponse newVpcOfferingResponse(VpcOffering offering) { 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 4e0707edf880..9d65c19479fb 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 Boolean isSpecifyAsNumber() { 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 20ca189994ef..7f41a1a106cb 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 IPAddressVO doInTransaction(TransactionStatus status) throws Insufficient return ipaddr; } + protected IPAddressVO getExistingSourceNatInVPC(Long vpcId) { + List ips = _ipAddressDao.listByAssociatedVpc(vpcId, true); + if (CollectionUtils.isEmpty(ips)) { + return null; + } + return ips.get(0); + } + protected IPAddressVO getExistingSourceNatInNetwork(long ownerId, Long networkId) { List addrs; Network guestNetwork = _networksDao.findById(networkId); @@ -1723,7 +1731,11 @@ protected boolean isSourceNatAvailableForNetwork(Account owner, IPAddressVO ipTo 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 void updateSourceNatIpAddress(IPAddressVO requestedIp, List }); } + @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 b959cc478d6f..0a2e679b723e 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 String getNicVlanValueForExternalVm(NicTO nic) { 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 805a897e0806..805ac4aed86c 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 @@ private boolean assignLBruleToNewVm(long vmId, AutoScaleVmGroupVO asGroup) { } 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 00863c28dd22..779d26d51f1c 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 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 void detectRulesConflict(FirewallRule newRule) throws NetworkRuleConflict 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 void detectRulesConflict(FirewallRule newRule) throws NetworkRuleConflict } // 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 void detectRulesConflict(FirewallRule newRule) throws NetworkRuleConflict } } + 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 ced1d781ab57..5f00261f3290 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.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 @@ private boolean isRollBackAllowedForProvider(LoadBalancerVO loadBalancer) { @Override @DB @ActionEvent(eventType = EventTypes.EVENT_ASSIGN_TO_LOAD_BALANCER_RULE, eventDescription = "assigning to load balancer", async = true) - public boolean assignToLoadBalancer(long loadBalancerId, List instanceIds, Map> vmIdIpMap, boolean isAutoScaleVM) { + public boolean assignToLoadBalancer(long loadBalancerId, List instanceIds, Map> vmIdIpMap, Map vmIdNetworkMap, boolean isAutoScaleVM) { CallContext ctx = CallContext.current(); Account caller = ctx.getCallingAccount(); @@ -1091,28 +1093,12 @@ public boolean assignToLoadBalancer(long loadBalancerId, List instanceIds, _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 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 boolean assignToLoadBalancer(long loadBalancerId, List instanceIds, 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 void doInTransactionWithoutResult(TransactionStatus status) { return success; } + protected Nic getVmNicInLoadBalancer(UserVm vm, LoadBalancerVO loadBalancer, Network loadBalancerNetwork, Map 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 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 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 LoadBalancer createPublicLoadBalancerRule(String xId, String name, String 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 LoadBalancer createPublicLoadBalancerRule(String xId, String name, String } 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 LoadBalancer createPublicLoadBalancerRule(String xId, String name, String 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 @@ private String validateCidr(String cidr) { @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 LoadBalancer createPublicLoadBalancer(final String xId, final String name _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 86d1fba038b7..3c3afb34e5a2 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 void doInTransactionWithoutResult(final TransactionStatus status) { } 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 void doInTransactionWithoutResult(final TransactionStatus status) { } } 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 void doInTransactionWithoutResult(final TransactionStatus status) { } } 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 void doInTransactionWithoutResult(final TransactionStatus status) { } } 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 void doInTransactionWithoutResult(final TransactionStatus status) { } } 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 void doInTransactionWithoutResult(final TransactionStatus status) { } } 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 void doInTransactionWithoutResult(final TransactionStatus status) { } } 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 VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { } 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 VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { 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 VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd) { public VpcOffering createVpcOffering(final String name, final String displayText, final List supportedServices, final Map> serviceProviders, final Map serviceCapabilityList, final NetUtils.InternetProtocol internetProtocol, final Long serviceOfferingId, final String externalProvider, final NetworkOffering.NetworkMode networkMode, List domainIds, List 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 VpcOffering createVpcOffering(final String name, final String displayText 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 detailsVO = new ArrayList<>(); @@ -755,7 +756,7 @@ public VpcOffering createVpcOffering(final String name, final String displayText @DB protected VpcOfferingVO createVpcOffering(final String name, final String displayText, final Map> 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() { @Override @@ -771,6 +772,7 @@ public VpcOfferingVO doInTransaction(final TransactionStatus status) { 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 boolean applyStaticRouteForVpcVpnIfNeeded(final Long vpcId, boolean updat 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 routes, final Account caller, final boolean updateRoutesInDB) throws ResourceUnavailableException { final boolean success = true; final List 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 1b864bd695fd..215a0e784bc6 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 void testDoScaleUp() throws ResourceUnavailableException, InsufficientCap 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>(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 f94fd0c0c3cd..bacef85479a2 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.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.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 void testApplyFWRules() { } } - @Test - public void testDetectRulesConflict() { - List ruleList = new ArrayList(); - 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 createExistingFirewallListRulesList(long existingNetworkId) { + List 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 sString = Arrays.asList("10.1.1.1/24","192.168.1.1/24"); List dString1 = Arrays.asList("10.1.1.1/25"); - List 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 createNewRuleList(long newNetworkId) { + List sString = Arrays.asList("10.1.1.1/24","192.168.1.1/24"); + List 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 ruleList = createExistingFirewallListRulesList(1L); + when(_firewallMgr._firewallDao.listByIpAndPurposeAndNotRevoked(3,null)).thenReturn(ruleList); + + List 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 ruleList = createExistingFirewallListRulesList(existingNetworkId); + when(_firewallMgr._firewallDao.listByIpAndPurposeAndNotRevoked(3,null)).thenReturn(ruleList); + + List 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 6b7677221bb6..1fc0517a727e 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.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 void testBothArgsEmpty() throws ResourceAllocationException, ResourceUnav 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 void testNicIsNotInNw() throws ResourceAllocationException, ResourceUnava 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 void testNicIsNotInNw() throws ResourceAllocationException, ResourceUnava _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 void testNicIsNotInNw() throws ResourceAllocationException, ResourceUnava 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 void tesSecIpNotSetToVm() throws ResourceAllocationException, ResourceUna 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 void tesSecIpNotSetToVm() throws ResourceAllocationException, ResourceUna _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 void testVmIdAlreadyExist() throws ResourceAllocationException, ResourceU 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 void testVmIdAlreadyExist() throws ResourceAllocationException, ResourceU _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 78655ba9a05e..cfa0e2edaae2 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.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 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 void testUpdateLoadBalancerRule5() throws Exception { 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 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 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 4f92c60e25ad..ff34d72c218d 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 void validateVpcPrivateGatewayTestAclFromDifferentVpcThrowsInvalidParamet 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 54d8d67b6f8d..de768388b449 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 void expungeLbVmRefs(List vmIds, Long batchSize) { 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 f3d31b2bf7e9..9570d35c618e 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.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 @@ def setUpClass(cls): 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 @@ def test_03_create_vpc_domain_vpc_offering(self): 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 000000000000..a56953db7872 --- /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 825159a2e53a..636c73209a3f 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -5227,6 +5227,8 @@ def create(cls, apiclient, services): 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 bc95772d6f7a..436c0cf1b604 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 ad091b218a83..0b9ed7684a89 100644 --- a/ui/src/views/network/LoadBalancing.vue +++ b/ui/src/views/network/LoadBalancing.vue @@ -204,6 +204,11 @@ {{ instance.loadbalancerruleinstance.displayname }} +
+ + {{ instance.loadbalancerruleinstance.nic[0].networkname }} + +
{{ ip }}
+ v-if="'vpcid' in resource && (!('associatednetworkid' in resource) || vpcConserveMode)"> {{ $t('label.select.tier') }} { + 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 8ab6559b12c3..ffa89e5b5814 100644 --- a/ui/src/views/network/PortForwarding.vue +++ b/ui/src/views/network/PortForwarding.vue @@ -117,6 +117,12 @@ +