This commit is contained in:
Nicolas Vazquez 2026-03-09 12:46:47 -03:00 committed by GitHub
commit 92a75f46ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 858 additions and 73 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -84,4 +84,6 @@ public interface VpcOffering extends InternalIdentity, Identity {
NetworkOffering.RoutingMode getRoutingMode();
Boolean isSpecifyAsNumber();
boolean isConserveMode();
}

View File

@ -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);

View File

@ -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";

View File

@ -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);

View File

@ -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");

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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'' ');

View File

@ -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`,

View File

@ -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) {

View File

@ -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);

View File

@ -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());

View File

@ -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());

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -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);

View File

@ -1740,6 +1740,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 +1765,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 +1794,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 +1839,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 +1868,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");

View File

@ -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);

View File

@ -24,13 +24,18 @@ 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.network.vpc.VpcOfferingVO;
import com.cloud.network.vpc.dao.VpcOfferingDao;
import com.cloud.user.AccountManager;
import com.cloud.user.DomainManager;
import com.cloud.utils.component.ComponentContext;
@ -43,6 +48,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 +82,10 @@ public class FirewallManagerTest {
IpAddressManager _ipAddrMgr;
@Mock
FirewallRulesDao _firewallDao;
@Mock
NetworkDao _networkDao;
@Mock
VpcOfferingDao vpcOfferingDao;
@Spy
@InjectMocks
@ -163,50 +173,102 @@ 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);
VpcOfferingVO vpcOffering = Mockito.mock(VpcOfferingVO.class);
when(_firewallMgr._networkDao.findById(2L)).thenReturn(newNetworkVO);
when(newNetworkVO.getVpcId()).thenReturn(vpcId);
when(_vpcMgr.getActiveVpc(vpcId)).thenReturn(vpc);
when(vpc.getVpcOfferingId()).thenReturn(1L);
when(vpcOfferingDao.findById(1L)).thenReturn(vpcOffering);
when(vpcOffering.isConserveMode()).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()
{

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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")

View File

@ -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
)

View File

@ -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):

View File

@ -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',

View File

@ -487,10 +487,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 +1022,8 @@ export default {
urlpath: '/'
},
healthMonitorLoading: false,
isNetrisZone: false
isNetrisZone: false,
vpcConserveMode: false
}
},
computed: {
@ -1079,10 +1080,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 +1845,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 +1865,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

View File

@ -216,10 +216,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 +467,8 @@ export default {
vmPageSize: 10,
vmCount: 0,
searchQuery: null,
cidrlist: ''
cidrlist: '',
vpcConserveMode: false
}
},
computed: {
@ -504,13 +505,24 @@ 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
}).catch(error => {
this.$notifyError(error)
})
},
fetchListTiers () {
this.selectedTier = null
this.tiers.loading = true
getAPI('listNetworks', {
@ -630,7 +642,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 +800,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]
@ -808,7 +820,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

View File

@ -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

View File

@ -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
}