From be58735b82588155b7541475259f8274549590b2 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 14 Nov 2019 03:37:59 -0300 Subject: [PATCH] Enable PVLAN support on L2 networks --- .../main/java/com/cloud/network/Network.java | 22 ++++ .../com/cloud/network/NetworkProfile.java | 5 + .../com/cloud/offering/NetworkOffering.java | 2 +- .../apache/cloudstack/api/ApiConstants.java | 1 + .../user/network/CreateNetworkCmd.java | 8 ++ .../service/NetworkOrchestrationService.java | 2 +- .../orchestration/NetworkOrchestrator.java | 13 +- .../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/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/vpc/MockNetworkManagerImpl.java | 2 +- ui/l10n/en.js | 5 + ui/scripts/sharedFunctions.js | 93 +++++++++++++- .../vmware/mo/HypervisorHostHelper.java | 32 +++-- 20 files changed, 386 insertions(+), 33 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 2cabd0214189..28528f1b558d 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.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 @@ enum GuestType { 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 void setIp6Address(String ip6Address) { 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 bf21c93c89f8..117f90e62a43 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 String getExternalId() { 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 45450745df1b..8ae90c574224 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 enum State { } 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/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index fb44a8a11f48..c727ff57ef86 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -355,6 +355,7 @@ public class ApiConstants { public static final String REMOVE_VLAN = "removevlan"; public static final String VLAN_ID = "vlanid"; public static final String ISOLATED_PVLAN = "isolatedpvlan"; + public static final String ISOLATED_PVLAN_TYPE = "isolatedpvlantype"; public static final String ISOLATION_URI = "isolationuri"; public static final String VM_AVAILABLE = "vmavailable"; public static final String VM_LIMIT = "vmlimit"; 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 d89205eb62d4..5dff7c9bf2bb 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 String getExternalId() { 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 94a4259a6897..08ed598f6556 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 @@ -179,7 +179,7 @@ void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationC 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/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 9e49148f4026..b4fce467c100 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 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; @@ -251,6 +254,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject NetworkDao _networksDao; @Inject + NetworkDetailsDao networkDetailsDao; + @Inject NicDao _nicDao; @Inject RulesManager _rulesMgr; @@ -698,6 +703,11 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { 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 +2178,7 @@ public void expungeNics(final VirtualMachineProfile vm) { 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); @@ -2438,6 +2448,7 @@ public Network doInTransaction(final TransactionStatus status) { } userNetwork.setBroadcastUri(NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan)); userNetwork.setBroadcastDomainType(BroadcastDomainType.Pvlan); + userNetwork.setPvlanType(isolatedPvlanType); } } 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 0c0bd4de6a12..6d591675f827 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 boolean isRollingRestart() { 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 b79097884890..d26b5ee80700 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 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 @@ protected VirtualMachineTO toVirtualMachineTO(VirtualMachineProfile vmProfile) { 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 817efcccb5ed..2995aa5448f1 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 Ternary, Network> doInTransaction(Transa 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 f440ced13b20..91c6771ca655 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 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; @@ -197,6 +198,9 @@ 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. */ @@ -1052,6 +1056,7 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac Long aclId = cmd.getAclId(); String isolatedPvlan = cmd.getIsolatedPvlan(); String externalId = cmd.getExternalId(); + String isolatedPvlanType = cmd.getIsolatedPvlanType(); // Validate network offering NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId); @@ -1239,14 +1244,24 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac } } - 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 advance 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))) { @@ -1278,7 +1293,7 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac 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!"); } @@ -1314,7 +1329,7 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac } 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) { @@ -1348,11 +1363,54 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac 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(secondaryVlanId) && (secondaryVlanId.startsWith("i") || secondaryVlanId.startsWith("c") || secondaryVlanId.startsWith("p"))) { + String[] parts = secondaryVlanId.split("-"); + if (parts.length < 2) { + throw new CloudRuntimeException("Could not properly parse the secondary VLAN ID to format TYPE-SVLANID: " + secondaryVlanId); + } + secondaryVlanId = parts[1]; + type = PVlanType.fromValue(parts[0]); + } + + if (isNotBlank(pvlanTypeStr)) { + PVlanType providedType = PVlanType.fromValue(pvlanTypeStr); + if (type != null && type != providedType) { + throw new CloudRuntimeException("The type set in the secondary VLAN ID: " + secondaryVlanId + " does not match the provided Private VLAN type: " + providedType); + } + type = providedType; + } + + if (isBlank(secondaryVlanId) && type != null && type == PVlanType.Promiscuous) { + secondaryVlanId = vlanId; + } + 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 Promiscouos, 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 @@ -1407,7 +1465,7 @@ public Network doInTransaction(TransactionStatus status) throws InsufficientCapa } 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) { @@ -4381,7 +4439,7 @@ public Network doInTransaction(TransactionStatus status) throws ResourceAllocati 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/GuestNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java index 9cd337447f9a..f67757c5eff5 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 Network design(final NetworkOffering offering, final DeploymentPlan plan, if (offering.isSpecifyVlan()) { network.setBroadcastUri(userSpecified.getBroadcastUri()); network.setState(State.Setup); + if (offering.getGuestType() == GuestType.L2 && 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 b1b1059c1236..815c45280a43 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -2561,7 +2561,7 @@ public Network createVpcGuestNetwork(final long ntwkOffId, final String name, fi // 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 bb4246eb7df9..5399c394749e 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -3304,7 +3304,7 @@ public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serv } 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()); @@ -6268,7 +6268,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) { 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 2e06039ccd55..2bba21515202 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 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 void setup() throws Exception { 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 000000000000..78304262642c --- /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("p-" + VLAN_ID_900, null, 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("i-" + VLAN_ID_901, null, 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("c-" + VLAN_ID_902, null, 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/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index e99c2a864cb6..6016907f3074 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -636,7 +636,7 @@ public boolean destroyNetwork(long networkId, ReservationContext context, boolea @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/ui/l10n/en.js b/ui/l10n/en.js index 4ce59e08599b..9cd417944b73 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -1536,6 +1536,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 d3e6fe870bee..0f57b011e125 100644 --- a/ui/scripts/sharedFunctions.js +++ b/ui/scripts/sharedFunctions.js @@ -356,6 +356,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', @@ -633,12 +668,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 } @@ -1004,6 +1039,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(); @@ -1013,9 +1049,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(); } }); @@ -1044,6 +1082,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: { @@ -1074,6 +1151,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/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 2eaa55a5768d..48b440adb496 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 @@ -579,8 +579,9 @@ public static Pair prepareNetwork(String physica // 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 = details.get(NetworkOffering.Detail.pvlanType); if (vid != null && spvlanid != null) { - setupPVlanPair(dvSwitchMo, morDvSwitch, vid, spvlanid); + setupPVlanPair(dvSwitchMo, morDvSwitch, vid, spvlanid, pvlanType); } VMwareDVSPortgroupPolicy portGroupPolicy = null; @@ -660,7 +661,8 @@ public static boolean isFeatureSupportedInVcenterApiVersion(String vCenterApiVer 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 +680,20 @@ private static void setupPVlanPair(DistributedVirtualSwitchMO dvSwitchMo, Manage 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 +1043,20 @@ public enum PvlanOperation { } 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) {