From ce896a477de31e530b4f28ae62fa42d9e0f1635c Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Fri, 7 Feb 2020 11:43:01 -0300 Subject: [PATCH] [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> --- .../main/java/com/cloud/network/Network.java | 22 ++ .../com/cloud/network/NetworkProfile.java | 5 + .../com/cloud/offering/NetworkOffering.java | 2 +- .../user/network/CreateNetworkCmd.java | 8 + .../service/NetworkOrchestrationService.java | 2 +- .../cloud/vm/VirtualMachineManagerImpl.java | 11 + .../orchestration/NetworkOrchestrator.java | 34 ++- .../com/cloud/network/dao/NetworkDao.java | 2 + .../com/cloud/network/dao/NetworkDaoImpl.java | 72 +++++ .../java/com/cloud/network/dao/NetworkVO.java | 11 + .../cloud/hypervisor/HypervisorGuruBase.java | 8 + .../cloud/network/IpAddressManagerImpl.java | 2 +- .../com/cloud/network/NetworkServiceImpl.java | 80 ++++- .../cloud/network/guru/DirectNetworkGuru.java | 4 + .../cloud/network/guru/GuestNetworkGuru.java | 4 + .../com/cloud/network/vpc/VpcManagerImpl.java | 2 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 4 +- .../network/CreatePrivateNetworkTest.java | 3 +- .../cloud/network/NetworkServiceImplTest.java | 120 ++++++++ .../com/cloud/network/dao/NetworkDaoTest.java | 64 ++-- .../com/cloud/vpc/MockNetworkManagerImpl.java | 2 +- .../com/cloud/vpc/dao/MockNetworkDaoImpl.java | 7 +- test/integration/smoke/test_network.py | 289 +++++++++++++++++- tools/marvin/marvin/lib/base.py | 4 + ui/l10n/en.js | 5 + ui/scripts/sharedFunctions.js | 96 +++++- .../main/java/com/cloud/utils/UriUtils.java | 11 + .../vmware/mo/HypervisorHostHelper.java | 33 +- 28 files changed, 835 insertions(+), 72 deletions(-) create mode 100644 server/src/test/java/com/cloud/network/NetworkServiceImplTest.java diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index 2cabd021418..28528f1b558 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -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, 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, I boolean isStrechedL2Network(); String getExternalId(); + + PVlanType getPvlanType(); } diff --git a/api/src/main/java/com/cloud/network/NetworkProfile.java b/api/src/main/java/com/cloud/network/NetworkProfile.java index bf21c93c89f..117f90e62a4 100644 --- a/api/src/main/java/com/cloud/network/NetworkProfile.java +++ b/api/src/main/java/com/cloud/network/NetworkProfile.java @@ -314,4 +314,9 @@ public class NetworkProfile implements Network { return externalId; } + @Override + public PVlanType getPvlanType() { + return null; + } + } diff --git a/api/src/main/java/com/cloud/offering/NetworkOffering.java b/api/src/main/java/com/cloud/offering/NetworkOffering.java index 45450745df1..8ae90c57422 100644 --- a/api/src/main/java/com/cloud/offering/NetworkOffering.java +++ b/api/src/main/java/com/cloud/offering/NetworkOffering.java @@ -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"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java index d89205eb62d..5dff7c9bf2b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java @@ -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) diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index b111a86c930..64c0e38613e 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -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); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index fedd3a38b5d..2c4079cd3fd 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -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 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); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 6fb957b38b1..6250905b4f8 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -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 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); } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java index 7da76c43206..ead57e660e5 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java @@ -122,4 +122,6 @@ public interface NetworkDao extends GenericDao, StateDao listNetworkVO(List idset); List listByAccountIdNetworkName(long accountId, String name); + + List listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java index 889bbc08467..3476908fb9e 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java @@ -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 GenericDaoBaseimplements Ne NetworkOfferingDao _ntwkOffDao; @Inject NetworkOpDao _ntwkOpDao; + @Inject + NetworkDetailsDao networkDetailsDao; TableGenerator _tgMacAddress; @@ -707,4 +711,72 @@ public class NetworkDaoImpl extends GenericDaoBaseimplements 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 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 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 searchVlans = UriUtils.expandPvlanUri(searchRange); + final List overlappingNetworks = new ArrayList<>(); + + final SearchCriteria 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 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; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java index 0c0bd4de6a1..6d591675f82 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java @@ -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; + } } diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java index b7909788489..d26b5ee8070 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java @@ -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; diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index 55981e80954..409b88f53c4 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -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 " diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 869fe67f5c8..3ebd38407ca 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -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 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 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() { @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 { diff --git a/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java index 21538a8a528..22811dadf43 100644 --- a/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java @@ -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); diff --git a/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java index 9cd337447f9..19927752ee0 100644 --- a/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java @@ -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(); diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 839280c3196..64c6f9ce4eb 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -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); diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index de204562bd8..ad2e453080e 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -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); diff --git a/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java b/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java index 2e06039ccd5..2bba2151520 100644 --- a/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java +++ b/server/src/test/java/com/cloud/network/CreatePrivateNetworkTest.java @@ -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); diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java new file mode 100644 index 00000000000..2520f3902ba --- /dev/null +++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java @@ -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 pair = service.getPrivateVlanPair(null, null, null); + Assert.assertNull(pair.first()); + Assert.assertNull(pair.second()); + } + + @Test + public void testGetPrivateVlanPairVlanPrimaryOnly() { + Pair pair = service.getPrivateVlanPair(null, null, VLAN_ID_900); + Assert.assertNull(pair.first()); + Assert.assertNull(pair.second()); + } + + @Test + public void testGetPrivateVlanPairVlanPrimaryPromiscuousType() { + Pair 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 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 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 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 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 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 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); + } + +} diff --git a/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java b/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java index 140c850a609..ca918691d09 100644 --- a/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java +++ b/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java @@ -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 tags = new ArrayList(); -// -// tags.add("a"); -// tags.add("b"); -// network.setTags(tags); -// -// network = dao.persist(network); -// List saveTags = network.getTags(); -// Assert.assertTrue(saveTags.size() == 2 && saveTags.contains("a") && saveTags.contains("b")); -// -// NetworkVO retrieved = dao.findById(1001l); -// List retrievedTags = retrieved.getTags(); -// Assert.assertTrue(retrievedTags.size() == 2 && retrievedTags.contains("a") && retrievedTags.contains("b")); -// -// List updateTags = new ArrayList(); -// 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)); + } } diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index d32c5649197..40a37a08e2d 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -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; diff --git a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java index e65b3270b42..89bc6256b4a 100644 --- a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java +++ b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java @@ -240,4 +240,9 @@ public class MockNetworkDaoImpl extends GenericDaoBase implemen public List listByAccountIdNetworkName(final long accountId, final String name) { return null; } -} \ No newline at end of file + + @Override + public List listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType) { + return null; + } +} diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py index e49f5475bee..b6d1bae6b16 100644 --- a/test/integration/smoke/test_network.py +++ b/test/integration/smoke/test_network.py @@ -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 diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index d233c97be48..8f604164790 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -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 diff --git a/ui/l10n/en.js b/ui/l10n/en.js index 5fdcb08e7f7..6412a6732fc 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -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", diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js index fa2a661ae9e..6151a6acc6e 100644 --- a/ui/scripts/sharedFunctions.js +++ b/ui/scripts/sharedFunctions.js @@ -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 diff --git a/utils/src/main/java/com/cloud/utils/UriUtils.java b/utils/src/main/java/com/cloud/utils/UriUtils.java index 14dce40d8c2..3796296dea7 100644 --- a/utils/src/main/java/com/cloud/utils/UriUtils.java +++ b/utils/src/main/java/com/cloud/utils/UriUtils.java @@ -624,4 +624,15 @@ public class UriUtils { } return !Collections.disjoint(vlans1, vlans2); } + + public static List expandPvlanUri(String pvlanRange) { + final List 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; + } } diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java index 2eaa55a5768..7826bb136a8 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java @@ -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 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 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) {