[Vmware] Enable PVLAN support on L2 networks (#3732)

* Enable PVLAN support on L2 networks

* Fix prevent null pointer on details

* Add marvin tests

* Fixes from comments

* Fix: missing pvlan type on plugniccommand

* Fix checks on network creation for vlans overlap

* Fix remove prefix from secondary vlan id

* Improve checks on physical network for pvlans

* Fix compatibility with previous pvlan creation

* Fix shared networks backwards pvlan compatibility

* Add ui fix for pvlan type not passed to api

* Add check for isolated vlan id overlap

* Include check for dynamic vlan reserved for secondary vlan

* Fix marvin tests errors

* Fix redundant imports

* Skip marvin test for pvlan if dvswitch is not present

* spelling

Co-authored-by: Andrija Panic <45762285+andrijapanicsb@users.noreply.github.com>
This commit is contained in:
Nicolas Vazquez 2020-02-07 11:43:01 -03:00 committed by GitHub
parent 70d1535df4
commit ce896a477d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 835 additions and 72 deletions

View File

@ -21,6 +21,8 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
@ -44,6 +46,24 @@ public interface Network extends ControlledEntity, StateObject<Network.State>, I
Shared, Isolated, L2
}
enum PVlanType {
Community, Isolated, Promiscuous;
static PVlanType fromValue(String type) {
if (StringUtils.isBlank(type)) {
return null;
} else if (type.equalsIgnoreCase("promiscuous") || type.equalsIgnoreCase("p")) {
return Promiscuous;
} else if (type.equalsIgnoreCase("community") || type.equalsIgnoreCase("c")) {
return Community;
} else if (type.equalsIgnoreCase("isolated") || type.equalsIgnoreCase("i")) {
return Isolated;
} else {
throw new InvalidParameterValueException("Unexpected Private VLAN type: " + type);
}
}
}
String updatingInSequence = "updatingInSequence";
String hideIpAddressUsage = "hideIpAddressUsage";
@ -416,4 +436,6 @@ public interface Network extends ControlledEntity, StateObject<Network.State>, I
boolean isStrechedL2Network();
String getExternalId();
PVlanType getPvlanType();
}

View File

@ -314,4 +314,9 @@ public class NetworkProfile implements Network {
return externalId;
}
@Override
public PVlanType getPvlanType() {
return null;
}
}

View File

@ -38,7 +38,7 @@ public interface NetworkOffering extends InfrastructureEntity, InternalIdentity,
}
public enum Detail {
InternalLbProvider, PublicLbProvider, servicepackageuuid, servicepackagedescription, PromiscuousMode, MacAddressChanges, ForgedTransmits, RelatedNetworkOffering, domainid, zoneid
InternalLbProvider, PublicLbProvider, servicepackageuuid, servicepackagedescription, PromiscuousMode, MacAddressChanges, ForgedTransmits, RelatedNetworkOffering, domainid, zoneid, pvlanType
}
public final static String SystemPublicNetwork = "System-Public-Network";

View File

@ -97,6 +97,10 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd {
@Parameter(name = ApiConstants.ISOLATED_PVLAN, type = CommandType.STRING, description = "the isolated private VLAN for this network")
private String isolatedPvlan;
@Parameter(name = ApiConstants.ISOLATED_PVLAN_TYPE, type = CommandType.STRING,
description = "the isolated private VLAN type for this network")
private String isolatedPvlanType;
@Parameter(name = ApiConstants.NETWORK_DOMAIN, type = CommandType.STRING, description = "network domain")
private String networkDomain;
@ -217,6 +221,10 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd {
return externalId;
}
public String getIsolatedPvlanType() {
return isolatedPvlanType;
}
@Override
public boolean isDisplay() {
if(displayNetwork == null)

View File

@ -180,7 +180,7 @@ public interface NetworkOrchestrationService {
Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, Account owner,
Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr,
Boolean displayNetworkEnabled, String isolatedPvlan, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException;
Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException;
UserDataServiceProvider getPasswordResetProvider(Network network);

View File

@ -39,6 +39,8 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.network.dao.NetworkDetailVO;
import com.cloud.network.dao.NetworkDetailsDao;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd;
@ -329,6 +331,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
private StorageManager storageMgr;
@Inject
private NetworkOfferingDetailsDao networkOfferingDetailsDao;
@Inject
private NetworkDetailsDao networkDetailsDao;
VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this);
@ -4059,6 +4063,13 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
final VMInstanceVO router = _vmDao.findById(vm.getId());
if (router.getState() == State.Running) {
try {
NetworkDetailVO pvlanTypeDetail = networkDetailsDao.findDetail(network.getId(), ApiConstants.ISOLATED_PVLAN_TYPE);
if (pvlanTypeDetail != null) {
Map<NetworkOffering.Detail, String> nicDetails = nic.getDetails() == null ? new HashMap<>() : nic.getDetails();
s_logger.debug("Found PVLAN type: " + pvlanTypeDetail.getValue() + " on network details, adding it as part of the PlugNicCommand");
nicDetails.putIfAbsent(NetworkOffering.Detail.pvlanType, pvlanTypeDetail.getValue());
nic.setDetails(nicDetails);
}
final PlugNicCommand plugNicCmd = new PlugNicCommand(nic, vm.getName(), vm.getType(), vm.getDetails());
final Commands cmds = new Commands(Command.OnError.Stop);
cmds.addCommand("plugnic", plugNicCmd);

View File

@ -38,7 +38,10 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.network.dao.NetworkDetailVO;
import com.cloud.network.dao.NetworkDetailsDao;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.cloud.entity.api.db.VMNetworkMapVO;
import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMNetworkMapDao;
@ -222,6 +225,8 @@ import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.google.common.base.Strings;
import static org.apache.commons.lang.StringUtils.isNotBlank;
/**
* NetworkManagerImpl implements NetworkManager.
*/
@ -251,6 +256,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
@Inject
NetworkDao _networksDao;
@Inject
NetworkDetailsDao networkDetailsDao;
@Inject
NicDao _nicDao;
@Inject
RulesManager _rulesMgr;
@ -698,6 +705,11 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
finalizeServicesAndProvidersForNetwork(offering, plan.getPhysicalNetworkId()));
networks.add(networkPersisted);
if (network.getPvlanType() != null) {
NetworkDetailVO detailVO = new NetworkDetailVO(networkPersisted.getId(), ApiConstants.ISOLATED_PVLAN_TYPE, network.getPvlanType().toString(), true);
networkDetailsDao.persist(detailVO);
}
if (predefined instanceof NetworkVO && guru instanceof NetworkGuruAdditionalFunctions){
final NetworkGuruAdditionalFunctions functions = (NetworkGuruAdditionalFunctions) guru;
functions.finalizeNetworkDesign(networkPersisted.getId(), ((NetworkVO)predefined).getVlanIdAsUUID());
@ -2168,7 +2180,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
public Network createGuestNetwork(final long networkOfferingId, final String name, final String displayText, final String gateway, final String cidr, String vlanId,
boolean bypassVlanOverlapCheck, String networkDomain, final Account owner, final Long domainId, final PhysicalNetwork pNtwk,
final long zoneId, final ACLType aclType, Boolean subdomainAccess, final Long vpcId, final String ip6Gateway, final String ip6Cidr,
final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
final NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
final DataCenterVO zone = _dcDao.findById(zoneId);
@ -2280,16 +2292,25 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
if (vlanSpecified) {
URI uri = BroadcastDomainType.fromString(vlanId);
// Aux: generate secondary URI for secondary VLAN ID (if provided) for performing checks
URI secondaryUri = isNotBlank(isolatedPvlan) ? BroadcastDomainType.fromString(isolatedPvlan) : null;
//don't allow to specify vlan tag used by physical network for dynamic vlan allocation
if (!(bypassVlanOverlapCheck && ntwkOff.getGuestType() == GuestType.Shared) && _dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(uri)).size() > 0) {
throw new InvalidParameterValueException("The VLAN tag " + vlanId + " is already being used for dynamic vlan allocation for the guest network in zone "
+ zone.getName());
}
if (secondaryUri != null && !(bypassVlanOverlapCheck && ntwkOff.getGuestType() == GuestType.Shared) &&
_dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(secondaryUri)).size() > 0) {
throw new InvalidParameterValueException("The VLAN tag " + isolatedPvlan + " is already being used for dynamic vlan allocation for the guest network in zone "
+ zone.getName());
}
if (! UuidUtils.validateUUID(vlanId)){
// For Isolated and L2 networks, don't allow to create network with vlan that already exists in the zone
if (ntwkOff.getGuestType() == GuestType.Isolated || !hasGuestBypassVlanOverlapCheck(bypassVlanOverlapCheck, ntwkOff)) {
if (ntwkOff.getGuestType() == GuestType.Isolated || ntwkOff.getGuestType() == GuestType.L2 || !hasGuestBypassVlanOverlapCheck(bypassVlanOverlapCheck, ntwkOff)) {
if (_networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), null).size() > 0) {
throw new InvalidParameterValueException("Network with vlan " + vlanId + " already exists or overlaps with other network vlans in zone " + zoneId);
} else if (secondaryUri != null && _networksDao.listByZoneAndUriAndGuestType(zoneId, secondaryUri.toString(), null).size() > 0) {
throw new InvalidParameterValueException("Network with vlan " + isolatedPvlan + " already exists or overlaps with other network vlans in zone " + zoneId);
} else {
final List<DataCenterVnetVO> dcVnets = _datacenterVnetDao.findVnet(zoneId, BroadcastDomainType.getValue(uri));
//for the network that is created as part of private gateway,
@ -2436,8 +2457,15 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
if (vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) {
throw new InvalidParameterValueException("Cannot support pvlan with untagged primary vlan!");
}
userNetwork.setBroadcastUri(NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan));
URI uri = NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan);
if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString(), isolatedPvlanType).size() > 0) {
throw new InvalidParameterValueException("Network with primary vlan " + vlanIdFinal +
" and secondary vlan " + isolatedPvlan + " type " + isolatedPvlanType +
" already exists or overlaps with other network pvlans in zone " + zoneId);
}
userNetwork.setBroadcastUri(uri);
userNetwork.setBroadcastDomainType(BroadcastDomainType.Pvlan);
userNetwork.setPvlanType(isolatedPvlanType);
}
}

View File

@ -122,4 +122,6 @@ public interface NetworkDao extends GenericDao<NetworkVO, Long>, StateDao<State,
List<NetworkVO> listNetworkVO(List<Long> idset);
List<NetworkVO> listByAccountIdNetworkName(long accountId, String name);
List<NetworkVO> listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType);
}

View File

@ -26,7 +26,9 @@ import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.TableGenerator;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.api.ApiConstants;
import org.springframework.stereotype.Component;
import com.cloud.network.Network;
@ -92,6 +94,8 @@ public class NetworkDaoImpl extends GenericDaoBase<NetworkVO, Long>implements Ne
NetworkOfferingDao _ntwkOffDao;
@Inject
NetworkOpDao _ntwkOpDao;
@Inject
NetworkDetailsDao networkDetailsDao;
TableGenerator _tgMacAddress;
@ -707,4 +711,72 @@ public class NetworkDaoImpl extends GenericDaoBase<NetworkVO, Long>implements Ne
return listBy(sc, null);
}
/**
* True when a requested PVLAN pair overlaps with any existing PVLAN pair within the same physical network, i.e when:
* - The requested exact PVLAN pair exists
* - The requested secondary VLAN ID is secondary VLAN ID of an existing PVLAN pair
* - The requested secondary VLAN ID is primary VLAN ID of an existing PVLAN pair
*/
protected boolean isNetworkOverlappingRequestedPvlan(Integer existingPrimaryVlan, Integer existingSecondaryVlan, Network.PVlanType existingPvlanType,
Integer requestedPrimaryVlan, Integer requestedSecondaryVlan, Network.PVlanType requestedPvlanType) {
if (existingPrimaryVlan == null || existingSecondaryVlan == null || requestedPrimaryVlan == null || requestedSecondaryVlan == null) {
throw new CloudRuntimeException(String.format("Missing VLAN ID while checking PVLAN pair (%s, %s)" +
" against existing pair (%s, %s)", existingPrimaryVlan, existingSecondaryVlan, requestedPrimaryVlan, requestedSecondaryVlan));
}
boolean exactMatch = existingPrimaryVlan.equals(requestedPrimaryVlan) && existingSecondaryVlan.equals(requestedSecondaryVlan);
boolean secondaryVlanUsed = requestedPvlanType != Network.PVlanType.Promiscuous && requestedSecondaryVlan.equals(existingPrimaryVlan) || requestedSecondaryVlan.equals(existingSecondaryVlan);
boolean isolatedMax = false;
boolean promiscuousMax = false;
if (requestedPvlanType == Network.PVlanType.Isolated && existingPrimaryVlan.equals(requestedPrimaryVlan) && existingPvlanType.equals(Network.PVlanType.Isolated)) {
isolatedMax = true;
} else if (requestedPvlanType == Network.PVlanType.Promiscuous && existingPrimaryVlan.equals(requestedPrimaryVlan) && existingPvlanType == Network.PVlanType.Promiscuous) {
promiscuousMax = true;
}
return exactMatch || secondaryVlanUsed || isolatedMax || promiscuousMax;
}
protected Network.PVlanType getNetworkPvlanType(long networkId, List<Integer> existingPvlan) {
Network.PVlanType existingPvlanType = null;
NetworkDetailVO pvlanTypeDetail = networkDetailsDao.findDetail(networkId, ApiConstants.ISOLATED_PVLAN_TYPE);
if (pvlanTypeDetail != null) {
existingPvlanType = Network.PVlanType.valueOf(pvlanTypeDetail.getValue());
} else {
existingPvlanType = existingPvlan.get(0).equals(existingPvlan.get(1)) ? Network.PVlanType.Promiscuous : Network.PVlanType.Isolated;
}
return existingPvlanType;
}
@Override
public List<NetworkVO> listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType) {
final URI searchUri = BroadcastDomainType.fromString(broadcastUri);
if (!searchUri.getScheme().equalsIgnoreCase("pvlan")) {
throw new CloudRuntimeException("PVLAN requested but URI is not in the expected format: " + searchUri.toString());
}
final String searchRange = BroadcastDomainType.getValue(searchUri);
final List<Integer> searchVlans = UriUtils.expandPvlanUri(searchRange);
final List<NetworkVO> overlappingNetworks = new ArrayList<>();
final SearchCriteria<NetworkVO> sc = PhysicalNetworkSearch.create();
sc.setParameters("physicalNetworkId", physicalNetworkId);
for (final NetworkVO network : listBy(sc)) {
if (network.getBroadcastUri() == null || !network.getBroadcastUri().getScheme().equalsIgnoreCase("pvlan")) {
continue;
}
final String networkVlanRange = BroadcastDomainType.getValue(network.getBroadcastUri());
if (networkVlanRange == null || networkVlanRange.isEmpty()) {
continue;
}
List<Integer> existingPvlan = UriUtils.expandPvlanUri(networkVlanRange);
Network.PVlanType existingPvlanType = getNetworkPvlanType(network.getId(), existingPvlan);
if (isNetworkOverlappingRequestedPvlan(existingPvlan.get(0), existingPvlan.get(1), existingPvlanType,
searchVlans.get(0), searchVlans.get(1), pVlanType)) {
overlappingNetworks.add(network);
break;
}
}
return overlappingNetworks;
}
}

View File

@ -181,6 +181,9 @@ public class NetworkVO implements Network {
@Transient
boolean rollingRestart = false;
@Transient
PVlanType pVlanType;
public NetworkVO() {
uuid = UUID.randomUUID().toString();
}
@ -661,4 +664,12 @@ public class NetworkVO implements Network {
public void setRollingRestart(boolean rollingRestart) {
this.rollingRestart = rollingRestart;
}
public PVlanType getPvlanType() {
return pVlanType;
}
public void setPvlanType(PVlanType pvlanType) {
this.pVlanType = pvlanType;
}
}

View File

@ -22,6 +22,8 @@ import java.util.UUID;
import javax.inject.Inject;
import com.cloud.network.dao.NetworkDetailVO;
import com.cloud.network.dao.NetworkDetailsDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.commons.collections.CollectionUtils;
@ -77,6 +79,8 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis
protected ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
@Inject
private ServiceOfferingDao _serviceOfferingDao;
@Inject
private NetworkDetailsDao networkDetailsDao;
@Override
public NicTO toNicTO(NicProfile profile) {
@ -182,6 +186,10 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis
details.putIfAbsent(NetworkOffering.Detail.MacAddressChanges, NetworkOrchestrationService.MacAddressChanges.value().toString());
details.putIfAbsent(NetworkOffering.Detail.ForgedTransmits, NetworkOrchestrationService.ForgedTransmits.value().toString());
}
NetworkDetailVO pvlantypeDetail = networkDetailsDao.findDetail(network.getId(), ApiConstants.ISOLATED_PVLAN_TYPE);
if (pvlantypeDetail != null) {
details.putIfAbsent(NetworkOffering.Detail.pvlanType, pvlantypeDetail.getValue());
}
nicTo.setDetails(details);
}
nics[i++] = nicTo;

View File

@ -1693,7 +1693,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
s_logger.debug("Creating network for account " + owner + " from the network offering id=" + requiredOfferings.get(0).getId()
+ " as a part of createVlanIpRange process");
guestNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(), owner.getAccountName() + "-network", owner.getAccountName()
+ "-network", null, null, null, false, null, owner, null, physicalNetwork, zoneId, ACLType.Account, null, null, null, null, true, null, null);
+ "-network", null, null, null, false, null, owner, null, physicalNetwork, zoneId, ACLType.Account, null, null, null, null, true, null, null, null);
if (guestNetwork == null) {
s_logger.warn("Failed to create default Virtual network for the account " + accountId + "in zone " + zoneId);
throw new CloudRuntimeException("Failed to create a Guest Isolated Networks with SourceNAT "

View File

@ -38,6 +38,7 @@ import java.util.UUID;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.network.Network.PVlanType;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.ApiConstants;
@ -200,6 +201,9 @@ import com.cloud.vm.dao.NicSecondaryIpVO;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;
/**
* NetworkServiceImpl implements NetworkService.
*/
@ -1062,6 +1066,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
Long aclId = cmd.getAclId();
String isolatedPvlan = cmd.getIsolatedPvlan();
String externalId = cmd.getExternalId();
String isolatedPvlanType = cmd.getIsolatedPvlanType();
// Validate network offering
NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
@ -1270,14 +1275,24 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
}
}
if (isolatedPvlan != null && (zone.getNetworkType() != NetworkType.Advanced || ntwkOff.getGuestType() != Network.GuestType.Shared)) {
throw new InvalidParameterValueException("Can only support create Private VLAN network with advance shared network!");
if (isNotBlank(isolatedPvlan) && (zone.getNetworkType() != NetworkType.Advanced || ntwkOff.getGuestType() == GuestType.Isolated)) {
throw new InvalidParameterValueException("Can only support create Private VLAN network with advanced shared or L2 network!");
}
if (isolatedPvlan != null && ipv6) {
if (isNotBlank(isolatedPvlan) && ipv6) {
throw new InvalidParameterValueException("Can only support create Private VLAN network with IPv4!");
}
Pair<String, PVlanType> pvlanPair = getPrivateVlanPair(isolatedPvlan, isolatedPvlanType, vlanId);
String secondaryVlanId = pvlanPair.first();
PVlanType privateVlanType = pvlanPair.second();
if ((isNotBlank(secondaryVlanId) || privateVlanType != null) && isBlank(vlanId)) {
throw new InvalidParameterValueException("VLAN ID has to be set in order to configure a Private VLAN");
}
performBasicPrivateVlanChecks(vlanId, secondaryVlanId, privateVlanType);
// Regular user can create Guest Isolated Source Nat enabled network only
if (_accountMgr.isNormalUser(caller.getId()) && (ntwkOff.getTrafficType() != TrafficType.Guest
|| ntwkOff.getGuestType() != Network.GuestType.Isolated && areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat))) {
@ -1309,7 +1324,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
throw new InvalidParameterValueException("Cannot support IPv6 on network offering with external devices!");
}
if (isolatedPvlan != null && providersConfiguredForExternalNetworking(ntwkProviders)) {
if (isNotBlank(secondaryVlanId) && providersConfiguredForExternalNetworking(ntwkProviders)) {
throw new InvalidParameterValueException("Cannot support private vlan on network offering with external devices!");
}
@ -1345,7 +1360,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
}
Network network = commitNetwork(networkOfferingId, gateway, startIP, endIP, netmask, networkDomain, vlanId, bypassVlanOverlapCheck, name, displayText, caller, physicalNetworkId, zoneId,
domainId, isDomainSpecific, subdomainAccess, vpcId, startIPv6, endIPv6, ip6Gateway, ip6Cidr, displayNetwork, aclId, isolatedPvlan, ntwkOff, pNtwk, aclType, owner, cidr, createVlan,
domainId, isDomainSpecific, subdomainAccess, vpcId, startIPv6, endIPv6, ip6Gateway, ip6Cidr, displayNetwork, aclId, secondaryVlanId, privateVlanType, ntwkOff, pNtwk, aclType, owner, cidr, createVlan,
externalId);
if (hideIpAddressUsage) {
@ -1379,11 +1394,54 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
return network;
}
/**
* Retrieve information (if set) for private VLAN when creating the network
*/
protected Pair<String, PVlanType> getPrivateVlanPair(String pvlanId, String pvlanTypeStr, String vlanId) {
String secondaryVlanId = pvlanId;
PVlanType type = null;
if (isNotBlank(pvlanTypeStr)) {
PVlanType providedType = PVlanType.fromValue(pvlanTypeStr);
type = providedType;
} else if (isNotBlank(vlanId) && isNotBlank(secondaryVlanId)) {
// Preserve the existing functionality
type = vlanId.equals(secondaryVlanId) ? PVlanType.Promiscuous : PVlanType.Isolated;
}
if (isBlank(secondaryVlanId) && type == PVlanType.Promiscuous) {
secondaryVlanId = vlanId;
}
if (isNotBlank(secondaryVlanId)) {
try {
Integer.parseInt(secondaryVlanId);
} catch (NumberFormatException e) {
throw new CloudRuntimeException("The secondary VLAN ID: " + secondaryVlanId + " is not in numeric format", e);
}
}
return new Pair<>(secondaryVlanId, type);
}
/**
* Basic checks for setting up private VLANs, considering the VLAN ID, secondary VLAN ID and private VLAN type
*/
protected void performBasicPrivateVlanChecks(String vlanId, String secondaryVlanId, PVlanType privateVlanType) {
if (isNotBlank(vlanId) && isBlank(secondaryVlanId) && privateVlanType != null && privateVlanType != PVlanType.Promiscuous) {
throw new InvalidParameterValueException("Private VLAN ID has not been set, therefore Promiscuous type is expected");
} else if (isNotBlank(vlanId) && isNotBlank(secondaryVlanId) && !vlanId.equalsIgnoreCase(secondaryVlanId) && privateVlanType == PVlanType.Promiscuous) {
throw new InvalidParameterValueException("Private VLAN type is set to Promiscuous, but VLAN ID and Secondary VLAN ID differ");
} else if (isNotBlank(vlanId) && isNotBlank(secondaryVlanId) && privateVlanType != null && privateVlanType != PVlanType.Promiscuous && vlanId.equalsIgnoreCase(secondaryVlanId)) {
throw new InvalidParameterValueException("Private VLAN type is set to " + privateVlanType + ", but VLAN ID and Secondary VLAN ID are equal");
}
}
private Network commitNetwork(final Long networkOfferingId, final String gateway, final String startIP, final String endIP, final String netmask, final String networkDomain, final String vlanId,
final Boolean bypassVlanOverlapCheck, final String name, final String displayText, final Account caller, final Long physicalNetworkId, final Long zoneId, final Long domainId,
final boolean isDomainSpecific, final Boolean subdomainAccessFinal, final Long vpcId, final String startIPv6, final String endIPv6, final String ip6Gateway, final String ip6Cidr,
final Boolean displayNetwork, final Long aclId, final String isolatedPvlan, final NetworkOfferingVO ntwkOff, final PhysicalNetwork pNtwk, final ACLType aclType, final Account ownerFinal,
final String cidr, final boolean createVlan, final String externalId) throws InsufficientCapacityException, ResourceAllocationException {
final Boolean bypassVlanOverlapCheck, final String name, final String displayText, final Account caller, final Long physicalNetworkId, final Long zoneId, final Long domainId,
final boolean isDomainSpecific, final Boolean subdomainAccessFinal, final Long vpcId, final String startIPv6, final String endIPv6, final String ip6Gateway, final String ip6Cidr,
final Boolean displayNetwork, final Long aclId, final String isolatedPvlan, final PVlanType isolatedPvlanType, final NetworkOfferingVO ntwkOff, final PhysicalNetwork pNtwk, final ACLType aclType, final Account ownerFinal,
final String cidr, final boolean createVlan, final String externalId) throws InsufficientCapacityException, ResourceAllocationException {
try {
Network network = Transaction.execute(new TransactionCallbackWithException<Network, Exception>() {
@Override
@ -1438,7 +1496,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
}
network = _networkMgr.createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, networkDomain, owner, sharedDomainId, pNtwk,
zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, displayNetwork, isolatedPvlan, externalId);
zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, displayNetwork, isolatedPvlan, isolatedPvlanType, externalId);
}
if (_accountMgr.isRootAdmin(caller.getId()) && createVlan && network != null) {
@ -4415,7 +4473,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
if (privateNetwork == null) {
//create Guest network
privateNetwork = _networkMgr.createGuestNetwork(ntwkOffFinal.getId(), networkName, displayText, gateway, cidr, uriString, false, null, owner, null, pNtwk,
pNtwk.getDataCenterId(), ACLType.Account, null, vpcId, null, null, true, null, null);
pNtwk.getDataCenterId(), ACLType.Account, null, vpcId, null, null, true, null, null,null);
if (privateNetwork != null) {
s_logger.debug("Successfully created guest network " + privateNetwork);
} else {

View File

@ -201,6 +201,10 @@ public class DirectNetworkGuru extends AdapterBase implements NetworkGuru {
if (userSpecified.getBroadcastDomainType() != null) {
config.setBroadcastDomainType(userSpecified.getBroadcastDomainType());
}
if (userSpecified.getPvlanType() != null) {
config.setPvlanType(userSpecified.getPvlanType());
}
}
boolean isSecurityGroupEnabled = _networkModel.areServicesSupportedByNetworkOffering(offering.getId(), Service.SecurityGroup);

View File

@ -214,6 +214,10 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur
if (offering.isSpecifyVlan()) {
network.setBroadcastUri(userSpecified.getBroadcastUri());
network.setState(State.Setup);
if (userSpecified.getPvlanType() != null) {
network.setBroadcastDomainType(BroadcastDomainType.Pvlan);
network.setPvlanType(userSpecified.getPvlanType());
}
}
} else {
final String guestNetworkCidr = dc.getGuestNetworkCidr();

View File

@ -2569,7 +2569,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
// 2) Create network
final Network guestNetwork = _ntwkMgr.createGuestNetwork(ntwkOffId, name, displayText, gateway, cidr, vlanId, false, networkDomain, owner, domainId, pNtwk, zoneId, aclType,
subdomainAccess, vpcId, null, null, isDisplayNetworkEnabled, null, externalId);
subdomainAccess, vpcId, null, null, isDisplayNetworkEnabled, null, null, externalId);
if (guestNetwork != null) {
guestNetwork.setNetworkACLId(aclId);

View File

@ -3354,7 +3354,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
s_logger.debug("Creating network for account " + owner + " from the network offering id=" + requiredOfferings.get(0).getId() + " as a part of deployVM process");
Network newNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(), owner.getAccountName() + "-network", owner.getAccountName() + "-network",
null, null, null, false, null, owner, null, physicalNetwork, zone.getId(), ACLType.Account, null, null, null, null, true, null,
null, null, null, false, null, owner, null, physicalNetwork, zone.getId(), ACLType.Account, null, null, null, null, true, null, null,
null);
if (newNetwork != null) {
defaultNetwork = _networkDao.findById(newNetwork.getId());
@ -6511,7 +6511,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
Network newNetwork = _networkMgr.createGuestNetwork(requiredOfferings.get(0).getId(), newAccount.getAccountName() + "-network",
newAccount.getAccountName() + "-network", null, null, null, false, null, newAccount,
null, physicalNetwork, zone.getId(), ACLType.Account, null, null,
null, null, true, null, null);
null, null, true, null, null, null);
// if the network offering has persistent set to true, implement the network
if (requiredOfferings.get(0).isPersistent()) {
DeployDestination dest = new DeployDestination(zone, null, null, null);

View File

@ -26,6 +26,7 @@ import java.util.UUID;
import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@ -123,7 +124,7 @@ public class CreatePrivateNetworkTest {
when(
networkService._networkMgr.createGuestNetwork(eq(ntwkOff.getId()), eq("bla"), eq("fake"), eq("10.1.1.1"), eq("10.1.1.0/24"), anyString(), anyBoolean(), anyString(),
eq(account), anyLong(), eq(physicalNetwork), eq(physicalNetwork.getDataCenterId()), eq(ACLType.Account), anyBoolean(), eq(1L), anyString(), anyString(),
anyBoolean(), anyString(), anyString())).thenReturn(net);
anyBoolean(), anyString(), Matchers.any(), anyString())).thenReturn(net);
when(networkService._privateIpDao.findByIpAndSourceNetworkId(net.getId(), "10.1.1.2")).thenReturn(null);
when(networkService._privateIpDao.findByIpAndSourceNetworkIdAndVpcId(eq(1L), anyString(), eq(1L))).thenReturn(null);

View File

@ -0,0 +1,120 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.network;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import org.junit.Assert;
import org.junit.Test;
public class NetworkServiceImplTest {
private NetworkServiceImpl service = new NetworkServiceImpl();
private static final String VLAN_ID_900 = "900";
private static final String VLAN_ID_901 = "901";
private static final String VLAN_ID_902 = "902";
@Test
public void testGetPrivateVlanPairNoVlans() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(null, null, null);
Assert.assertNull(pair.first());
Assert.assertNull(pair.second());
}
@Test
public void testGetPrivateVlanPairVlanPrimaryOnly() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(null, null, VLAN_ID_900);
Assert.assertNull(pair.first());
Assert.assertNull(pair.second());
}
@Test
public void testGetPrivateVlanPairVlanPrimaryPromiscuousType() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(null, Network.PVlanType.Promiscuous.toString(), VLAN_ID_900);
Assert.assertEquals(VLAN_ID_900, pair.first());
Assert.assertEquals(Network.PVlanType.Promiscuous, pair.second());
}
@Test
public void testGetPrivateVlanPairPromiscuousType() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(VLAN_ID_900, Network.PVlanType.Promiscuous.toString(), VLAN_ID_900);
Assert.assertEquals(VLAN_ID_900, pair.first());
Assert.assertEquals(Network.PVlanType.Promiscuous, pair.second());
}
@Test
public void testGetPrivateVlanPairPromiscuousTypeOnSecondaryVlanId() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(VLAN_ID_900, "promiscuous", VLAN_ID_900);
Assert.assertEquals(VLAN_ID_900, pair.first());
Assert.assertEquals(Network.PVlanType.Promiscuous, pair.second());
}
@Test
public void testGetPrivateVlanPairIsolatedType() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(VLAN_ID_901, Network.PVlanType.Isolated.toString(), VLAN_ID_900);
Assert.assertEquals(VLAN_ID_901, pair.first());
Assert.assertEquals(Network.PVlanType.Isolated, pair.second());
}
@Test
public void testGetPrivateVlanPairIsolatedTypeOnSecondaryVlanId() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(VLAN_ID_901, "isolated", VLAN_ID_900);
Assert.assertEquals(VLAN_ID_901, pair.first());
Assert.assertEquals(Network.PVlanType.Isolated, pair.second());
}
@Test
public void testGetPrivateVlanPairCommunityType() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(VLAN_ID_902, Network.PVlanType.Community.toString(), VLAN_ID_900);
Assert.assertEquals(VLAN_ID_902, pair.first());
Assert.assertEquals(Network.PVlanType.Community, pair.second());
}
@Test
public void testGetPrivateVlanPairCommunityTypeOnSecondaryVlanId() {
Pair<String, Network.PVlanType> pair = service.getPrivateVlanPair(VLAN_ID_902, "community", VLAN_ID_900);
Assert.assertEquals(VLAN_ID_902, pair.first());
Assert.assertEquals(Network.PVlanType.Community, pair.second());
}
@Test(expected = CloudRuntimeException.class)
public void testPerformBasicChecksPromiscuousTypeExpectedIsolatedSet() {
service.performBasicPrivateVlanChecks(VLAN_ID_900, VLAN_ID_900, Network.PVlanType.Isolated);
}
@Test(expected = CloudRuntimeException.class)
public void testPerformBasicChecksPromiscuousTypeExpectedCommunitySet() {
service.performBasicPrivateVlanChecks(VLAN_ID_900, VLAN_ID_900, Network.PVlanType.Community);
}
@Test(expected = CloudRuntimeException.class)
public void testPerformBasicChecksPromiscuousTypeExpectedSecondaryVlanNullIsolatedSet() {
service.performBasicPrivateVlanChecks(VLAN_ID_900, null, Network.PVlanType.Isolated);
}
@Test(expected = CloudRuntimeException.class)
public void testPerformBasicChecksPromiscuousTypeExpectedSecondaryVlanNullCommunitySet() {
service.performBasicPrivateVlanChecks(VLAN_ID_900, null, Network.PVlanType.Community);
}
@Test(expected = CloudRuntimeException.class)
public void testPerformBasicChecksPromiscuousTypeExpectedDifferentVlanIds() {
service.performBasicPrivateVlanChecks(VLAN_ID_900, VLAN_ID_901, Network.PVlanType.Promiscuous);
}
}

View File

@ -16,46 +16,40 @@
// under the License.
package com.cloud.network.dao;
import com.cloud.network.Network;
import junit.framework.TestCase;
import org.junit.Assert;
import org.mockito.Spy;
public class NetworkDaoTest extends TestCase {
public void testTags() {
// NetworkDaoImpl dao = ComponentLocator.inject(NetworkDaoImpl.class);
//
// dao.expunge(1001l);
// NetworkVO network = new NetworkVO(1001, TrafficType.Control, GuestType.Shared, Mode.Dhcp, BroadcastDomainType.Native, 1, 1, 1, 1, 1001, "Name", "DisplayText", false, true, true, null, null);
// network.setGuruName("guru_name");
// List<String> tags = new ArrayList<String>();
//
// tags.add("a");
// tags.add("b");
// network.setTags(tags);
//
// network = dao.persist(network);
// List<String> saveTags = network.getTags();
// Assert.assertTrue(saveTags.size() == 2 && saveTags.contains("a") && saveTags.contains("b"));
//
// NetworkVO retrieved = dao.findById(1001l);
// List<String> retrievedTags = retrieved.getTags();
// Assert.assertTrue(retrievedTags.size() == 2 && retrievedTags.contains("a") && retrievedTags.contains("b"));
//
// List<String> updateTags = new ArrayList<String>();
// updateTags.add("e");
// updateTags.add("f");
// retrieved.setTags(updateTags);
// dao.update(retrieved.getId(), retrieved);
//
// retrieved = dao.findById(1001l);
// retrievedTags = retrieved.getTags();
// Assert.assertTrue("Unable to retrieve back the data updated", retrievedTags.size() == 2 && retrievedTags.contains("e") && retrievedTags.contains("f"));
//
// dao.expunge(1001l);
@Spy
private NetworkDaoImpl dao = new NetworkDaoImpl();
private static final Integer existingPrimaryVlan = 900;
private static final Integer existingSecondaryVlan = 901;
private static final Integer requestedVlan = 902;
public void testNetworkOverlappingExactPair() {
Assert.assertTrue(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingSecondaryVlan, Network.PVlanType.Isolated,
existingPrimaryVlan, existingSecondaryVlan, Network.PVlanType.Isolated));
Assert.assertTrue(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingSecondaryVlan, Network.PVlanType.Isolated,
existingPrimaryVlan, existingSecondaryVlan, Network.PVlanType.Community));
}
public void testListBy() {
// NetworkDaoImpl dao = ComponentLocator.inject(NetworkDaoImpl.class);
//
// dao.listBy(1l, 1l, 1l, "192.168.192.0/24");
public void testNetworkOverlappingPromiscuous() {
Assert.assertTrue(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingPrimaryVlan, Network.PVlanType.Promiscuous,
existingPrimaryVlan, existingPrimaryVlan, Network.PVlanType.Promiscuous));
}
public void testNetworkOverlappingIsolated() {
Assert.assertTrue(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingSecondaryVlan, Network.PVlanType.Isolated,
existingPrimaryVlan, requestedVlan, Network.PVlanType.Isolated));
}
public void testNetworkOverlappingMultipleCommunityAllowed() {
Assert.assertFalse(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingSecondaryVlan, Network.PVlanType.Community,
existingPrimaryVlan, requestedVlan, Network.PVlanType.Community));
}
}

View File

@ -641,7 +641,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches
@Override
public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain,
Account owner, Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6,
String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan, String externalId) throws ConcurrentOperationException, InsufficientCapacityException,
String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId) throws ConcurrentOperationException, InsufficientCapacityException,
ResourceAllocationException {
// TODO Auto-generated method stub
return null;

View File

@ -240,4 +240,9 @@ public class MockNetworkDaoImpl extends GenericDaoBase<NetworkVO, Long> implemen
public List<NetworkVO> listByAccountIdNetworkName(final long accountId, final String name) {
return null;
}
@Override
public List<NetworkVO> listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType) {
return null;
}
}

View File

@ -34,7 +34,9 @@ from marvin.lib.base import (Account,
Network,
NetworkOffering,
LoadBalancerRule,
Router)
Router,
NIC,
Cluster)
from marvin.lib.common import (get_domain,
get_zone,
get_test_template,
@ -47,6 +49,7 @@ from marvin.lib.common import (get_domain,
list_configurations,
verifyGuestTrafficPortGroups)
from nose.plugins.attrib import attr
from marvin.lib.decoratorGenerators import skipTestIf
from ddt import ddt, data
# Import System modules
import time
@ -1532,3 +1535,287 @@ class TestL2Networks(cloudstackTestCase):
)
return
class TestPrivateVlansL2Networks(cloudstackTestCase):
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.cleanup = [
]
def tearDown(self):
cleanup_resources(self.apiclient, self.cleanup)
return
@classmethod
def setUpClass(cls):
testClient = super(TestPrivateVlansL2Networks, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.services = testClient.getParsedTestDataConfig()
cls.domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
cls.hypervisor = testClient.getHypervisorInfo()
cls.services['mode'] = cls.zone.networktype
# Supported hypervisor = Vmware using dvSwitches for guest traffic
isVmware = False
isDvSwitch = False
if cls.hypervisor.lower() in ["vmware"]:
isVmware = True
clusters = Cluster.list(cls.apiclient, zoneid=cls.zone.id, hypervisor=cls.hypervisor)
for cluster in clusters:
if cluster.resourcedetails.guestvswitchtype == "vmwaredvs":
# Test only if cluster uses dvSwitch
isDvSwitch = True
break
supported = isVmware and isDvSwitch
cls.vmwareHypervisorDvSwitchesForGuestTrafficNotPresent = not supported
cls._cleanup = []
if supported:
cls.account = Account.create(
cls.apiclient,
cls.services["account"],
admin=True,
domainid=cls.domain.id
)
cls.template = get_test_template(
cls.apiclient,
cls.zone.id,
cls.hypervisor
)
cls.service_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offerings"]["tiny"]
)
cls.services["network"]["zoneid"] = cls.zone.id
cls.services['mode'] = cls.zone.networktype
cls.services["small"]["zoneid"] = cls.zone.id
cls.services["small"]["template"] = cls.template.id
cls.services["l2-network-pvlan-community-1"] = {
"name": "Test Network L2 PVLAN Community 1",
"displaytext": "Test Network L2 PVLAN Community 1",
"vlan": 900,
"isolatedpvlan": "901",
"isolatedpvlantype": "community"
}
cls.services["l2-network-pvlan-community-2"] = {
"name": "Test Network L2 PVLAN Community 2",
"displaytext": "Test Network L2 PVLAN Community 2",
"vlan": 900,
"isolatedpvlan": "902",
"isolatedpvlantype": "community"
}
cls.services["l2-network-pvlan-promiscuous"] = {
"name": "Test Network L2 PVLAN Promiscuous",
"displaytext": "Test Network L2 PVLAN Promiscuous",
"vlan": 900,
"isolatedpvlan" : "900",
"isolatedpvlantype": "promiscuous"
}
cls.services["l2-network-pvlan-isolated"] = {
"name": "Test Network L2 PVLAN Isolated",
"displaytext": "Test Network L2 PVLAN Isolated",
"vlan": 900,
"isolatedpvlan": "903",
"isolatedpvlantype": "isolated"
}
cls.l2_network_offering = NetworkOffering.create(
cls.apiclient,
cls.services["l2-network_offering"],
specifyvlan=True
)
cls.isolated_network_offering = NetworkOffering.create(
cls.apiclient,
cls.services["network_offering"]
)
cls.l2_network_offering.update(cls.apiclient, state='Enabled')
cls.isolated_network_offering.update(cls.apiclient, state='Enabled')
cls.l2_pvlan_community1 = Network.create(
cls.apiclient,
cls.services["l2-network-pvlan-community-1"],
zoneid=cls.zone.id,
networkofferingid=cls.l2_network_offering.id
)
cls.l2_pvlan_community2 = Network.create(
cls.apiclient,
cls.services["l2-network-pvlan-community-2"],
zoneid=cls.zone.id,
networkofferingid=cls.l2_network_offering.id
)
cls.l2_pvlan_isolated = Network.create(
cls.apiclient,
cls.services["l2-network-pvlan-isolated"],
zoneid=cls.zone.id,
networkofferingid=cls.l2_network_offering.id
)
cls.l2_pvlan_promiscuous = Network.create(
cls.apiclient,
cls.services["l2-network-pvlan-promiscuous"],
zoneid=cls.zone.id,
networkofferingid=cls.l2_network_offering.id
)
cls.isolated_network = Network.create(
cls.apiclient,
cls.services["isolated_network"],
zoneid=cls.zone.id,
networkofferingid=cls.isolated_network_offering.id,
accountid=cls.account.name,
domainid=cls.account.domainid
)
cls._cleanup = [
cls.l2_pvlan_promiscuous,
cls.l2_pvlan_isolated,
cls.l2_pvlan_community1,
cls.l2_pvlan_community2,
cls.isolated_network,
cls.l2_network_offering,
cls.isolated_network_offering,
cls.service_offering,
cls.account,
]
return
@classmethod
def tearDownClass(cls):
try:
cleanup_resources(cls.apiclient, cls._cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return
def deploy_vm_multiple_nics(self, name, l2net):
"""
Deploy VM on L2 network and isolated network so VM can get an IP, to use with arping command for isolation test
"""
self.services["small"]["name"] = name
vm = VirtualMachine.create(
self.apiclient,
self.services["small"],
accountid=self.account.name,
domainid=self.account.domainid,
serviceofferingid=self.service_offering.id,
networkids=[self.isolated_network.id, l2net.id],
mode=self.services["mode"]
)
return vm
def is_vm_l2_isolated_from_dest(self, vm, eth_device, dest_ip):
"""
True if VM is isolated from dest IP - using arping through the NIC on L2 network:
If arping can reach destination, then response is greater than 3 (no reply)
"""
ssh_client = vm.get_ssh_client()
response = ssh_client.execute("/usr/sbin/arping -c 5 -I %s %s" % (eth_device, str(dest_ip)))
return len(response) == 3
def enable_l2_nic(self, vm):
vm_ip = list(filter(lambda x: x['networkid'] == self.isolated_network.id, vm.nic))[0]['ipaddress']
ssh_client = vm.get_ssh_client()
eth_device = "eth0"
if len(ssh_client.execute("/sbin/ifconfig %s | grep %s" % (eth_device, vm_ip))) > 0:
eth_device = "eth1"
ssh_client.execute("/sbin/ifconfig %s up" % eth_device)
return vm_ip, eth_device
@attr(tags=["advanced", "advancedns", "smoke", "pvlan"], required_hardware="true")
@skipTestIf("vmwareHypervisorDvSwitchesForGuestTrafficNotPresent")
def test_l2_network_pvlan_connectivity(self):
try:
vm_community1_one = self.deploy_vm_multiple_nics("vmcommunity1one", self.l2_pvlan_community1)
vm_community1_two = self.deploy_vm_multiple_nics("vmcommunity1two", self.l2_pvlan_community1)
vm_community2 = self.deploy_vm_multiple_nics("vmcommunity2", self.l2_pvlan_community2)
vm_isolated1 = self.deploy_vm_multiple_nics("vmisolated1", self.l2_pvlan_isolated)
vm_isolated2 = self.deploy_vm_multiple_nics("vmisolated2", self.l2_pvlan_isolated)
vm_promiscuous1 = self.deploy_vm_multiple_nics("vmpromiscuous1", self.l2_pvlan_promiscuous)
vm_promiscuous2 = self.deploy_vm_multiple_nics("vmpromiscuous2", self.l2_pvlan_promiscuous)
self.cleanup.append(vm_community1_one)
self.cleanup.append(vm_community1_two)
self.cleanup.append(vm_community2)
self.cleanup.append(vm_isolated1)
self.cleanup.append(vm_isolated2)
self.cleanup.append(vm_promiscuous1)
self.cleanup.append(vm_promiscuous2)
vm_community1_one_ip, vm_community1_one_eth = self.enable_l2_nic(vm_community1_one)
vm_community1_two_ip, vm_community1_two_eth = self.enable_l2_nic(vm_community1_two)
vm_community2_ip, vm_community2_eth = self.enable_l2_nic(vm_community2)
vm_isolated1_ip, vm_isolated1_eth = self.enable_l2_nic(vm_isolated1)
vm_isolated2_ip, vm_isolated2_eth = self.enable_l2_nic(vm_isolated2)
vm_promiscuous1_ip, vm_promiscuous1_eth = self.enable_l2_nic(vm_promiscuous1)
vm_promiscuous2_ip, vm_promiscuous2_eth = self.enable_l2_nic(vm_promiscuous2)
# Community PVLAN checks
different_community_isolated = self.is_vm_l2_isolated_from_dest(vm_community1_one, vm_community1_one_eth, vm_community2_ip)
same_community_isolated = self.is_vm_l2_isolated_from_dest(vm_community1_one, vm_community1_one_eth, vm_community1_two_ip)
community_to_promiscuous_isolated = self.is_vm_l2_isolated_from_dest(vm_community1_one, vm_community1_one_eth, vm_promiscuous1_ip)
community_to_isolated = self.is_vm_l2_isolated_from_dest(vm_community1_one, vm_community1_one_eth, vm_isolated1_ip)
self.assertTrue(
different_community_isolated,
"VMs on different community PVLANs must be isolated on layer 2"
)
self.assertFalse(
same_community_isolated,
"VMs on the same community PVLAN must not be isolated on layer 2"
)
self.assertFalse(
community_to_promiscuous_isolated,
"VMs on community PVLANs must not be isolated on layer 2 to VMs on promiscuous PVLAN"
)
self.assertTrue(
community_to_isolated,
"VMs on community PVLANs must be isolated on layer 2 to Vms on isolated PVLAN"
)
# Isolated PVLAN checks
same_isolated = self.is_vm_l2_isolated_from_dest(vm_isolated1, vm_isolated1_eth, vm_isolated2_ip)
isolated_to_community_isolated = self.is_vm_l2_isolated_from_dest(vm_isolated1, vm_isolated1_eth, vm_community1_one_ip)
self.assertTrue(
same_isolated,
"VMs on isolated PVLANs must be isolated on layer 2"
)
self.assertTrue(
isolated_to_community_isolated,
"VMs on isolated PVLANs must be isolated on layer 2 to Vms on community PVLAN"
)
# Promiscuous PVLAN checks
same_promiscuous = self.is_vm_l2_isolated_from_dest(vm_promiscuous1, vm_promiscuous1_eth, vm_promiscuous2_ip)
prom_to_community_isolated = self.is_vm_l2_isolated_from_dest(vm_promiscuous1, vm_promiscuous1_eth, vm_community1_one_ip)
prom_to_isolated = self.is_vm_l2_isolated_from_dest(vm_promiscuous1, vm_promiscuous1_eth, vm_isolated1_ip)
self.assertFalse(
same_promiscuous,
"VMs on promiscuous PVLANs must not be isolated on layer 2"
)
self.assertFalse(
prom_to_community_isolated,
"VMs on promiscuous PVLANs must not be isolated on layer 2 to Vms on isolated PVLAN"
)
self.assertFalse(
prom_to_isolated,
"VMs on promiscuous PVLANs must not be isolated on layer 2 to Vms on community PVLAN"
)
except Exception as e:
self.fail("Failing test. Error: %s" % e)
return

View File

@ -3067,6 +3067,10 @@ class Network:
cmd.vlan = services["vlan"]
if "acltype" in services:
cmd.acltype = services["acltype"]
if "isolatedpvlan" in services:
cmd.isolatedpvlan = services["isolatedpvlan"]
if "isolatedpvlantype" in services:
cmd.isolatedpvlantype = services["isolatedpvlantype"]
if accountid:
cmd.account = accountid

View File

@ -1550,6 +1550,11 @@ var dictionary = {
"label.search":"Search",
"label.secondary.ips":"Secondary IPs",
"label.secondary.isolated.vlan.id":"Secondary Isolated VLAN ID",
"label.secondary.isolated.vlan.type":"Secondary Isolated VLAN Type",
"label.secondary.isolated.vlan.type.community":"Community",
"label.secondary.isolated.vlan.type.isolated":"Isolated",
"label.secondary.isolated.vlan.type.none":"None",
"label.secondary.isolated.vlan.type.promiscuous":"Promiscuous",
"label.secondary.staging.store":"Secondary Staging Store",
"label.secondary.staging.store.details":"Secondary Staging Store details",
"label.secondary.storage":"Secondary Storage",

View File

@ -358,6 +358,41 @@ var addGuestNetworkDialog = {
isolatedpvlanId: {
label: 'label.secondary.isolated.vlan.id'
},
pvlanType: {
label: 'label.secondary.isolated.vlan.type',
isHidden: true,
select: function (args) {
var type = [{
id: 'none',
description: _l('label.secondary.isolated.vlan.type.none')
}, {
id: 'community',
description: _l('label.secondary.isolated.vlan.type.community')
}, {
id: 'isolated',
description: _l('label.secondary.isolated.vlan.type.isolated')
}, {
id: 'promiscuous',
description: _l('label.secondary.isolated.vlan.type.promiscuous')
}
];
args.response.success({
data: type
});
args.$select.change(function () {
var $form = $(this).closest('form');
var pvlanType = $(this).val();
if (pvlanType === 'none' || pvlanType === 'promiscuous') {
$form.find('.form-item[rel=isolatedpvlanId]').hide();
} else if (pvlanType === 'isolated' || pvlanType === 'community') {
$form.find('.form-item[rel=isolatedpvlanId]').css('display', 'inline-block');
}
})
}
},
scope: {
label: 'label.scope',
@ -635,12 +670,12 @@ var addGuestNetworkDialog = {
$form.find('.form-item[rel=vlanId]').hide();
cloudStack.dialog.createFormField.validation.required.remove($form.find('.form-item[rel=vlanId]')); //make vlanId optional
$form.find('.form-item[rel=isolatedpvlanId]').hide();
$form.find('.form-item[rel=pvlanType]').hide();
} else {
$form.find('.form-item[rel=vlanId]').css('display', 'inline-block');
cloudStack.dialog.createFormField.validation.required.add($form.find('.form-item[rel=vlanId]')); //make vlanId required
$form.find('.form-item[rel=isolatedpvlanId]').css('display', 'inline-block');
$form.find('.form-item[rel=pvlanType]').css('display', 'inline-block');
}
return false; //break each loop
}
@ -816,6 +851,9 @@ var addGuestNetworkDialog = {
if (args.data.hideipaddressusage != null && args.data.hideipaddressusage) {
array1.push("&hideipaddressusage=true")
}
if (args.$form.find('.form-item[rel=pvlanType]').css('display') != 'none' && args.data.pvlanType != 'none') {
array1.push("&isolatedpvlantype=" + args.data.pvlanType);
}
$.ajax({
url: createURL("createNetwork" + array1.join("")),
@ -1006,6 +1044,7 @@ var addL2GuestNetwork = {
args.$select.change(function() {
var $vlan = args.$select.closest('form').find('[rel=vlan]');
var $bypassVlanOverlapCheck = args.$select.closest('form').find('[rel=bypassVlanOverlapCheck]');
var $pvlanType = args.$select.closest('form').find('[rel=pvlanType]');
var networkOffering = $.grep(
networkOfferingObjs, function(netoffer) {
return netoffer.id == args.$select.val();
@ -1015,9 +1054,11 @@ var addL2GuestNetwork = {
if (networkOffering.specifyvlan) {
$vlan.css('display', 'inline-block');
$bypassVlanOverlapCheck.css('display', 'inline-block');
$pvlanType.css('display', 'inline-block');
} else {
$vlan.hide();
$bypassVlanOverlapCheck.hide();
$pvlanType.hide();
}
});
@ -1046,6 +1087,45 @@ var addL2GuestNetwork = {
isBoolean: true,
isHidden: true
},
pvlanId: {
label: 'label.secondary.isolated.vlan.id',
isHidden: true
},
pvlanType: {
label: 'label.secondary.isolated.vlan.type',
isHidden: true,
select: function (args) {
var type = [{
id: 'none',
description: _l('label.secondary.isolated.vlan.type.none')
}, {
id: 'community',
description: _l('label.secondary.isolated.vlan.type.community')
}, {
id: 'isolated',
description: _l('label.secondary.isolated.vlan.type.isolated')
}, {
id: 'promiscuous',
description: _l('label.secondary.isolated.vlan.type.promiscuous')
}
];
args.response.success({
data: type
});
args.$select.change(function () {
var $form = $(this).closest('form');
var pvlanType = $(this).val();
if (pvlanType === 'none' || pvlanType === 'promiscuous') {
$form.find('.form-item[rel=pvlanId]').hide();
} else if (pvlanType === 'isolated' || pvlanType === 'community') {
$form.find('.form-item[rel=pvlanId]').css('display', 'inline-block');
}
})
}
},
account: {
label: 'label.account',
validation: {
@ -1076,6 +1156,18 @@ var addL2GuestNetwork = {
});
}
if (args.$form.find('.form-item[rel=pvlanId]').css('display') != 'none') {
$.extend(dataObj, {
isolatedpvlan: args.data.pvlanId
});
}
if (args.$form.find('.form-item[rel=pvlanType]').css('display') != 'none' && args.data.pvlanType != 'none') {
$.extend(dataObj, {
isolatedpvlantype: args.data.pvlanType
});
}
if (args.data.domain != null && args.data.domain.length > 0) {
$.extend(dataObj, {
domainid: args.data.domain

View File

@ -624,4 +624,15 @@ public class UriUtils {
}
return !Collections.disjoint(vlans1, vlans2);
}
public static List<Integer> expandPvlanUri(String pvlanRange) {
final List<Integer> expandedVlans = new ArrayList<>();
if (Strings.isNullOrEmpty(pvlanRange)) {
return expandedVlans;
}
String[] parts = pvlanRange.split("-i");
expandedVlans.add(Integer.parseInt(parts[0]));
expandedVlans.add(Integer.parseInt(parts[1]));
return expandedVlans;
}
}

View File

@ -38,6 +38,7 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
@ -579,8 +580,9 @@ public class HypervisorHostHelper {
// First, if both vlan id and pvlan id are provided, we need to
// reconfigure the DVSwitch to have a tuple <vlan id, pvlan id> of
// type isolated.
String pvlanType = MapUtils.isNotEmpty(details) ? details.get(NetworkOffering.Detail.pvlanType) : null;
if (vid != null && spvlanid != null) {
setupPVlanPair(dvSwitchMo, morDvSwitch, vid, spvlanid);
setupPVlanPair(dvSwitchMo, morDvSwitch, vid, spvlanid, pvlanType);
}
VMwareDVSPortgroupPolicy portGroupPolicy = null;
@ -660,7 +662,8 @@ public class HypervisorHostHelper {
return vCenterApiVersion.compareTo(minVcenterApiVersionForFeature) >= 0 ? true : false;
}
private static void setupPVlanPair(DistributedVirtualSwitchMO dvSwitchMo, ManagedObjectReference morDvSwitch, Integer vid, Integer spvlanid) throws Exception {
private static void setupPVlanPair(DistributedVirtualSwitchMO dvSwitchMo, ManagedObjectReference morDvSwitch, Integer vid, Integer spvlanid, String pvlanType) throws Exception {
s_logger.debug(String.format("Setting up PVLAN on dvSwitch %s with the following information: %s %s %s", dvSwitchMo.getName(), vid, spvlanid, pvlanType));
Map<Integer, HypervisorHostHelper.PvlanType> vlanmap = dvSwitchMo.retrieveVlanPvlan(vid, spvlanid, morDvSwitch);
if (!vlanmap.isEmpty()) {
// Then either vid or pvlanid or both are already being used. Check how.
@ -678,25 +681,20 @@ public class HypervisorHostHelper {
s_logger.error(msg);
throw new Exception(msg);
}
} else {
if (vlanmap.containsKey(spvlanid) && !vlanmap.get(spvlanid).equals(HypervisorHostHelper.PvlanType.isolated)) {
// This PVLAN ID is already setup as a non-isolated vlan id on the DVS. Throw an exception.
String msg = "Specified secondary PVLAN ID " + spvlanid + " is already in use as a " + vlanmap.get(spvlanid).toString() + " VLAN in the DVSwitch";
s_logger.error(msg);
throw new Exception(msg);
}
}
}
// First create a DVSconfig spec.
VMwareDVSConfigSpec dvsSpec = new VMwareDVSConfigSpec();
// Next, add the required primary and secondary vlan config specs to the dvs config spec.
if (!vlanmap.containsKey(vid)) {
VMwareDVSPvlanConfigSpec ppvlanConfigSpec = createDVPortPvlanConfigSpec(vid, vid, PvlanType.promiscuous, PvlanOperation.add);
dvsSpec.getPvlanConfigSpec().add(ppvlanConfigSpec);
}
if (!vid.equals(spvlanid) && !vlanmap.containsKey(spvlanid)) {
VMwareDVSPvlanConfigSpec spvlanConfigSpec = createDVPortPvlanConfigSpec(vid, spvlanid, PvlanType.isolated, PvlanOperation.add);
PvlanType selectedType = StringUtils.isNotBlank(pvlanType) ? PvlanType.fromStr(pvlanType) : PvlanType.isolated;
VMwareDVSPvlanConfigSpec spvlanConfigSpec = createDVPortPvlanConfigSpec(vid, spvlanid, selectedType, PvlanOperation.add);
dvsSpec.getPvlanConfigSpec().add(spvlanConfigSpec);
}
@ -1046,7 +1044,20 @@ public class HypervisorHostHelper {
}
public enum PvlanType {
promiscuous, isolated, community, // We don't use Community
promiscuous, isolated, community;
public static PvlanType fromStr(String val) {
if (StringUtils.isBlank(val)) {
return null;
} else if (val.equalsIgnoreCase("promiscuous")) {
return promiscuous;
} else if (val.equalsIgnoreCase("community")) {
return community;
} else if (val.equalsIgnoreCase("isolated")) {
return isolated;
}
return null;
}
}
public static VMwareDVSPvlanConfigSpec createDVPortPvlanConfigSpec(int vlanId, int secondaryVlanId, PvlanType pvlantype, PvlanOperation operation) {