Skip to content

Commit ce896a4

Browse files
[Vmware] Enable PVLAN support on L2 networks (apache#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 <[email protected]>
1 parent 70d1535 commit ce896a4

File tree

28 files changed

+835
-72
lines changed

28 files changed

+835
-72
lines changed

api/src/main/java/com/cloud/network/Network.java

+22
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.ArrayList;
2222
import java.util.List;
2323

24+
import com.cloud.exception.InvalidParameterValueException;
25+
import org.apache.commons.lang.StringUtils;
2426
import org.apache.commons.lang.builder.ToStringBuilder;
2527
import org.apache.commons.lang.builder.ToStringStyle;
2628

@@ -44,6 +46,24 @@ enum GuestType {
4446
Shared, Isolated, L2
4547
}
4648

49+
enum PVlanType {
50+
Community, Isolated, Promiscuous;
51+
52+
static PVlanType fromValue(String type) {
53+
if (StringUtils.isBlank(type)) {
54+
return null;
55+
} else if (type.equalsIgnoreCase("promiscuous") || type.equalsIgnoreCase("p")) {
56+
return Promiscuous;
57+
} else if (type.equalsIgnoreCase("community") || type.equalsIgnoreCase("c")) {
58+
return Community;
59+
} else if (type.equalsIgnoreCase("isolated") || type.equalsIgnoreCase("i")) {
60+
return Isolated;
61+
} else {
62+
throw new InvalidParameterValueException("Unexpected Private VLAN type: " + type);
63+
}
64+
}
65+
}
66+
4767
String updatingInSequence = "updatingInSequence";
4868
String hideIpAddressUsage = "hideIpAddressUsage";
4969

@@ -416,4 +436,6 @@ public void setIp6Address(String ip6Address) {
416436
boolean isStrechedL2Network();
417437

418438
String getExternalId();
439+
440+
PVlanType getPvlanType();
419441
}

api/src/main/java/com/cloud/network/NetworkProfile.java

+5
Original file line numberDiff line numberDiff line change
@@ -314,4 +314,9 @@ public String getExternalId() {
314314
return externalId;
315315
}
316316

317+
@Override
318+
public PVlanType getPvlanType() {
319+
return null;
320+
}
321+
317322
}

api/src/main/java/com/cloud/offering/NetworkOffering.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public enum State {
3838
}
3939

4040
public enum Detail {
41-
InternalLbProvider, PublicLbProvider, servicepackageuuid, servicepackagedescription, PromiscuousMode, MacAddressChanges, ForgedTransmits, RelatedNetworkOffering, domainid, zoneid
41+
InternalLbProvider, PublicLbProvider, servicepackageuuid, servicepackagedescription, PromiscuousMode, MacAddressChanges, ForgedTransmits, RelatedNetworkOffering, domainid, zoneid, pvlanType
4242
}
4343

4444
public final static String SystemPublicNetwork = "System-Public-Network";

api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java

+8
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd {
9797
@Parameter(name = ApiConstants.ISOLATED_PVLAN, type = CommandType.STRING, description = "the isolated private VLAN for this network")
9898
private String isolatedPvlan;
9999

100+
@Parameter(name = ApiConstants.ISOLATED_PVLAN_TYPE, type = CommandType.STRING,
101+
description = "the isolated private VLAN type for this network")
102+
private String isolatedPvlanType;
103+
100104
@Parameter(name = ApiConstants.NETWORK_DOMAIN, type = CommandType.STRING, description = "network domain")
101105
private String networkDomain;
102106

@@ -217,6 +221,10 @@ public String getExternalId() {
217221
return externalId;
218222
}
219223

224+
public String getIsolatedPvlanType() {
225+
return isolatedPvlanType;
226+
}
227+
220228
@Override
221229
public boolean isDisplay() {
222230
if(displayNetwork == null)

engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationC
180180

181181
Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, Account owner,
182182
Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr,
183-
Boolean displayNetworkEnabled, String isolatedPvlan, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException;
183+
Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException;
184184

185185
UserDataServiceProvider getPasswordResetProvider(Network network);
186186

engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java

+11
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import javax.inject.Inject;
4040
import javax.naming.ConfigurationException;
4141

42+
import com.cloud.network.dao.NetworkDetailVO;
43+
import com.cloud.network.dao.NetworkDetailsDao;
4244
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
4345
import org.apache.cloudstack.api.ApiConstants;
4446
import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd;
@@ -329,6 +331,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
329331
private StorageManager storageMgr;
330332
@Inject
331333
private NetworkOfferingDetailsDao networkOfferingDetailsDao;
334+
@Inject
335+
private NetworkDetailsDao networkDetailsDao;
332336

333337
VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this);
334338

@@ -4059,6 +4063,13 @@ public boolean plugNic(final Network network, final NicTO nic, final VirtualMach
40594063
final VMInstanceVO router = _vmDao.findById(vm.getId());
40604064
if (router.getState() == State.Running) {
40614065
try {
4066+
NetworkDetailVO pvlanTypeDetail = networkDetailsDao.findDetail(network.getId(), ApiConstants.ISOLATED_PVLAN_TYPE);
4067+
if (pvlanTypeDetail != null) {
4068+
Map<NetworkOffering.Detail, String> nicDetails = nic.getDetails() == null ? new HashMap<>() : nic.getDetails();
4069+
s_logger.debug("Found PVLAN type: " + pvlanTypeDetail.getValue() + " on network details, adding it as part of the PlugNicCommand");
4070+
nicDetails.putIfAbsent(NetworkOffering.Detail.pvlanType, pvlanTypeDetail.getValue());
4071+
nic.setDetails(nicDetails);
4072+
}
40624073
final PlugNicCommand plugNicCmd = new PlugNicCommand(nic, vm.getName(), vm.getType(), vm.getDetails());
40634074
final Commands cmds = new Commands(Command.OnError.Stop);
40644075
cmds.addCommand("plugnic", plugNicCmd);

engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java

+31-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@
3838
import javax.inject.Inject;
3939
import javax.naming.ConfigurationException;
4040

41+
import com.cloud.network.dao.NetworkDetailVO;
42+
import com.cloud.network.dao.NetworkDetailsDao;
4143
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
44+
import org.apache.cloudstack.api.ApiConstants;
4245
import org.apache.cloudstack.context.CallContext;
4346
import org.apache.cloudstack.engine.cloud.entity.api.db.VMNetworkMapVO;
4447
import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMNetworkMapDao;
@@ -222,6 +225,8 @@
222225
import com.cloud.vm.dao.VMInstanceDao;
223226
import com.google.common.base.Strings;
224227

228+
import static org.apache.commons.lang.StringUtils.isNotBlank;
229+
225230
/**
226231
* NetworkManagerImpl implements NetworkManager.
227232
*/
@@ -251,6 +256,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
251256
@Inject
252257
NetworkDao _networksDao;
253258
@Inject
259+
NetworkDetailsDao networkDetailsDao;
260+
@Inject
254261
NicDao _nicDao;
255262
@Inject
256263
RulesManager _rulesMgr;
@@ -698,6 +705,11 @@ public void doInTransactionWithoutResult(final TransactionStatus status) {
698705
finalizeServicesAndProvidersForNetwork(offering, plan.getPhysicalNetworkId()));
699706
networks.add(networkPersisted);
700707

708+
if (network.getPvlanType() != null) {
709+
NetworkDetailVO detailVO = new NetworkDetailVO(networkPersisted.getId(), ApiConstants.ISOLATED_PVLAN_TYPE, network.getPvlanType().toString(), true);
710+
networkDetailsDao.persist(detailVO);
711+
}
712+
701713
if (predefined instanceof NetworkVO && guru instanceof NetworkGuruAdditionalFunctions){
702714
final NetworkGuruAdditionalFunctions functions = (NetworkGuruAdditionalFunctions) guru;
703715
functions.finalizeNetworkDesign(networkPersisted.getId(), ((NetworkVO)predefined).getVlanIdAsUUID());
@@ -2168,7 +2180,7 @@ public void expungeNics(final VirtualMachineProfile vm) {
21682180
public Network createGuestNetwork(final long networkOfferingId, final String name, final String displayText, final String gateway, final String cidr, String vlanId,
21692181
boolean bypassVlanOverlapCheck, String networkDomain, final Account owner, final Long domainId, final PhysicalNetwork pNtwk,
21702182
final long zoneId, final ACLType aclType, Boolean subdomainAccess, final Long vpcId, final String ip6Gateway, final String ip6Cidr,
2171-
final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
2183+
final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException {
21722184

21732185
final NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId);
21742186
final DataCenterVO zone = _dcDao.findById(zoneId);
@@ -2280,16 +2292,25 @@ public Network createGuestNetwork(final long networkOfferingId, final String nam
22802292

22812293
if (vlanSpecified) {
22822294
URI uri = BroadcastDomainType.fromString(vlanId);
2295+
// Aux: generate secondary URI for secondary VLAN ID (if provided) for performing checks
2296+
URI secondaryUri = isNotBlank(isolatedPvlan) ? BroadcastDomainType.fromString(isolatedPvlan) : null;
22832297
//don't allow to specify vlan tag used by physical network for dynamic vlan allocation
22842298
if (!(bypassVlanOverlapCheck && ntwkOff.getGuestType() == GuestType.Shared) && _dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(uri)).size() > 0) {
22852299
throw new InvalidParameterValueException("The VLAN tag " + vlanId + " is already being used for dynamic vlan allocation for the guest network in zone "
22862300
+ zone.getName());
22872301
}
2302+
if (secondaryUri != null && !(bypassVlanOverlapCheck && ntwkOff.getGuestType() == GuestType.Shared) &&
2303+
_dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(secondaryUri)).size() > 0) {
2304+
throw new InvalidParameterValueException("The VLAN tag " + isolatedPvlan + " is already being used for dynamic vlan allocation for the guest network in zone "
2305+
+ zone.getName());
2306+
}
22882307
if (! UuidUtils.validateUUID(vlanId)){
22892308
// For Isolated and L2 networks, don't allow to create network with vlan that already exists in the zone
2290-
if (ntwkOff.getGuestType() == GuestType.Isolated || !hasGuestBypassVlanOverlapCheck(bypassVlanOverlapCheck, ntwkOff)) {
2309+
if (ntwkOff.getGuestType() == GuestType.Isolated || ntwkOff.getGuestType() == GuestType.L2 || !hasGuestBypassVlanOverlapCheck(bypassVlanOverlapCheck, ntwkOff)) {
22912310
if (_networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), null).size() > 0) {
22922311
throw new InvalidParameterValueException("Network with vlan " + vlanId + " already exists or overlaps with other network vlans in zone " + zoneId);
2312+
} else if (secondaryUri != null && _networksDao.listByZoneAndUriAndGuestType(zoneId, secondaryUri.toString(), null).size() > 0) {
2313+
throw new InvalidParameterValueException("Network with vlan " + isolatedPvlan + " already exists or overlaps with other network vlans in zone " + zoneId);
22932314
} else {
22942315
final List<DataCenterVnetVO> dcVnets = _datacenterVnetDao.findVnet(zoneId, BroadcastDomainType.getValue(uri));
22952316
//for the network that is created as part of private gateway,
@@ -2436,8 +2457,15 @@ public Network doInTransaction(final TransactionStatus status) {
24362457
if (vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) {
24372458
throw new InvalidParameterValueException("Cannot support pvlan with untagged primary vlan!");
24382459
}
2439-
userNetwork.setBroadcastUri(NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan));
2460+
URI uri = NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan);
2461+
if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString(), isolatedPvlanType).size() > 0) {
2462+
throw new InvalidParameterValueException("Network with primary vlan " + vlanIdFinal +
2463+
" and secondary vlan " + isolatedPvlan + " type " + isolatedPvlanType +
2464+
" already exists or overlaps with other network pvlans in zone " + zoneId);
2465+
}
2466+
userNetwork.setBroadcastUri(uri);
24402467
userNetwork.setBroadcastDomainType(BroadcastDomainType.Pvlan);
2468+
userNetwork.setPvlanType(isolatedPvlanType);
24412469
}
24422470
}
24432471

engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java

+2
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,6 @@ public interface NetworkDao extends GenericDao<NetworkVO, Long>, StateDao<State,
122122
List<NetworkVO> listNetworkVO(List<Long> idset);
123123

124124
List<NetworkVO> listByAccountIdNetworkName(long accountId, String name);
125+
126+
List<NetworkVO> listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType);
125127
}

engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java

+72
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import javax.inject.Inject;
2727
import javax.persistence.TableGenerator;
2828

29+
import com.cloud.utils.exception.CloudRuntimeException;
2930
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
31+
import org.apache.cloudstack.api.ApiConstants;
3032
import org.springframework.stereotype.Component;
3133

3234
import com.cloud.network.Network;
@@ -92,6 +94,8 @@ public class NetworkDaoImpl extends GenericDaoBase<NetworkVO, Long>implements Ne
9294
NetworkOfferingDao _ntwkOffDao;
9395
@Inject
9496
NetworkOpDao _ntwkOpDao;
97+
@Inject
98+
NetworkDetailsDao networkDetailsDao;
9599

96100
TableGenerator _tgMacAddress;
97101

@@ -707,4 +711,72 @@ public List<NetworkVO> listByAccountIdNetworkName(final long accountId, final St
707711

708712
return listBy(sc, null);
709713
}
714+
715+
/**
716+
* True when a requested PVLAN pair overlaps with any existing PVLAN pair within the same physical network, i.e when:
717+
* - The requested exact PVLAN pair exists
718+
* - The requested secondary VLAN ID is secondary VLAN ID of an existing PVLAN pair
719+
* - The requested secondary VLAN ID is primary VLAN ID of an existing PVLAN pair
720+
*/
721+
protected boolean isNetworkOverlappingRequestedPvlan(Integer existingPrimaryVlan, Integer existingSecondaryVlan, Network.PVlanType existingPvlanType,
722+
Integer requestedPrimaryVlan, Integer requestedSecondaryVlan, Network.PVlanType requestedPvlanType) {
723+
if (existingPrimaryVlan == null || existingSecondaryVlan == null || requestedPrimaryVlan == null || requestedSecondaryVlan == null) {
724+
throw new CloudRuntimeException(String.format("Missing VLAN ID while checking PVLAN pair (%s, %s)" +
725+
" against existing pair (%s, %s)", existingPrimaryVlan, existingSecondaryVlan, requestedPrimaryVlan, requestedSecondaryVlan));
726+
}
727+
boolean exactMatch = existingPrimaryVlan.equals(requestedPrimaryVlan) && existingSecondaryVlan.equals(requestedSecondaryVlan);
728+
boolean secondaryVlanUsed = requestedPvlanType != Network.PVlanType.Promiscuous && requestedSecondaryVlan.equals(existingPrimaryVlan) || requestedSecondaryVlan.equals(existingSecondaryVlan);
729+
boolean isolatedMax = false;
730+
boolean promiscuousMax = false;
731+
if (requestedPvlanType == Network.PVlanType.Isolated && existingPrimaryVlan.equals(requestedPrimaryVlan) && existingPvlanType.equals(Network.PVlanType.Isolated)) {
732+
isolatedMax = true;
733+
} else if (requestedPvlanType == Network.PVlanType.Promiscuous && existingPrimaryVlan.equals(requestedPrimaryVlan) && existingPvlanType == Network.PVlanType.Promiscuous) {
734+
promiscuousMax = true;
735+
}
736+
return exactMatch || secondaryVlanUsed || isolatedMax || promiscuousMax;
737+
}
738+
739+
protected Network.PVlanType getNetworkPvlanType(long networkId, List<Integer> existingPvlan) {
740+
Network.PVlanType existingPvlanType = null;
741+
NetworkDetailVO pvlanTypeDetail = networkDetailsDao.findDetail(networkId, ApiConstants.ISOLATED_PVLAN_TYPE);
742+
if (pvlanTypeDetail != null) {
743+
existingPvlanType = Network.PVlanType.valueOf(pvlanTypeDetail.getValue());
744+
} else {
745+
existingPvlanType = existingPvlan.get(0).equals(existingPvlan.get(1)) ? Network.PVlanType.Promiscuous : Network.PVlanType.Isolated;
746+
}
747+
return existingPvlanType;
748+
}
749+
750+
@Override
751+
public List<NetworkVO> listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType) {
752+
final URI searchUri = BroadcastDomainType.fromString(broadcastUri);
753+
if (!searchUri.getScheme().equalsIgnoreCase("pvlan")) {
754+
throw new CloudRuntimeException("PVLAN requested but URI is not in the expected format: " + searchUri.toString());
755+
}
756+
final String searchRange = BroadcastDomainType.getValue(searchUri);
757+
final List<Integer> searchVlans = UriUtils.expandPvlanUri(searchRange);
758+
final List<NetworkVO> overlappingNetworks = new ArrayList<>();
759+
760+
final SearchCriteria<NetworkVO> sc = PhysicalNetworkSearch.create();
761+
sc.setParameters("physicalNetworkId", physicalNetworkId);
762+
763+
for (final NetworkVO network : listBy(sc)) {
764+
if (network.getBroadcastUri() == null || !network.getBroadcastUri().getScheme().equalsIgnoreCase("pvlan")) {
765+
continue;
766+
}
767+
final String networkVlanRange = BroadcastDomainType.getValue(network.getBroadcastUri());
768+
if (networkVlanRange == null || networkVlanRange.isEmpty()) {
769+
continue;
770+
}
771+
List<Integer> existingPvlan = UriUtils.expandPvlanUri(networkVlanRange);
772+
Network.PVlanType existingPvlanType = getNetworkPvlanType(network.getId(), existingPvlan);
773+
if (isNetworkOverlappingRequestedPvlan(existingPvlan.get(0), existingPvlan.get(1), existingPvlanType,
774+
searchVlans.get(0), searchVlans.get(1), pVlanType)) {
775+
overlappingNetworks.add(network);
776+
break;
777+
}
778+
}
779+
780+
return overlappingNetworks;
781+
}
710782
}

engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java

+11
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ public class NetworkVO implements Network {
181181
@Transient
182182
boolean rollingRestart = false;
183183

184+
@Transient
185+
PVlanType pVlanType;
186+
184187
public NetworkVO() {
185188
uuid = UUID.randomUUID().toString();
186189
}
@@ -661,4 +664,12 @@ public boolean isRollingRestart() {
661664
public void setRollingRestart(boolean rollingRestart) {
662665
this.rollingRestart = rollingRestart;
663666
}
667+
668+
public PVlanType getPvlanType() {
669+
return pVlanType;
670+
}
671+
672+
public void setPvlanType(PVlanType pvlanType) {
673+
this.pVlanType = pvlanType;
674+
}
664675
}

0 commit comments

Comments
 (0)