Skip to content

Commit d644d6e

Browse files
committed
reject a trunk nic with a VLAN 1 nic on one VM, and only treat Guest VLAN 1 nics as needing a legacy host, including in host planning
1 parent 57c0c05 commit d644d6e

8 files changed

Lines changed: 299 additions & 17 deletions

File tree

‎core/src/main/java/com/cloud/agent/api/VlanTrunkMigrationHelper.java‎

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.cloud.agent.api;
1818

1919
import java.net.URI;
20+
import java.util.Collection;
2021
import java.util.List;
2122
import java.util.Map;
2223

@@ -73,11 +74,11 @@ public static boolean vmNeedsPostMigrationVlanTrunkMembership(long vmId, long ds
7374
return nicDao.listByVmId(vmId).stream().anyMatch(nic -> nicNeedsManualVlanMembership(nic, destVlanFilteringEnabled));
7475
}
7576

76-
// A multi-VLAN trunk nic needs a VLAN-filtering host; a VLAN 1 nic needs a legacy host, as the shared bridge's PVID is VLAN 1. Null when the host fits.
77+
// A multi-VLAN trunk nic needs a VLAN-filtering host; a Guest VLAN 1 nic needs a legacy host, as the shared bridge's PVID is VLAN 1. Null when the host fits.
7778
public static String getVlanFilteringUnsuitableReason(long vmId, long hostId, NicDao nicDao, NetworkDao networkDao, HostDetailsDao hostDetailsDao) {
7879
List<NicVO> nics = nicDao.listByVmId(vmId);
79-
boolean hasTrunkNic = nics.stream().anyMatch(NicVO::getMultiNetwork);
80-
boolean hasNativeVlanNic = nics.stream().anyMatch(nic -> !nic.getMultiNetwork() && isNativeVlan(getEffectiveBroadcastUri(nic, networkDao)));
80+
boolean hasTrunkNic = hasTrunkNic(nics);
81+
boolean hasNativeVlanNic = hasNativeVlanGuestNic(nics, networkDao);
8182
if (!hasTrunkNic && !hasNativeVlanNic) {
8283
return null;
8384
}
@@ -93,7 +94,20 @@ public static String getVlanFilteringUnsuitableReason(long vmId, long hostId, Ni
9394

9495
// For a network about to be plugged into a running Instance on this host.
9596
public static String getNativeVlanUnsuitableReason(Network network, long hostId, HostDetailsDao hostDetailsDao) {
96-
return isNativeVlan(network.getBroadcastUri()) && isVlanFilteringEnabled(hostId, hostDetailsDao) ? NATIVE_VLAN_ON_FILTERING_HOST_REASON : null;
97+
return isNativeVlanGuestNetwork(network) && isVlanFilteringEnabled(hostId, hostDetailsDao) ? NATIVE_VLAN_ON_FILTERING_HOST_REASON : null;
98+
}
99+
100+
public static boolean hasTrunkNic(Collection<NicVO> nics) {
101+
return nics.stream().anyMatch(NicVO::getMultiNetwork);
102+
}
103+
104+
// Only Guest nics ride the shared bridge; Public, Management and Storage nics use their own bridges.
105+
public static boolean hasNativeVlanGuestNic(Collection<NicVO> nics, NetworkDao networkDao) {
106+
return nics.stream().anyMatch(nic -> isNativeVlanGuestNic(nic, networkDao));
107+
}
108+
109+
public static boolean isNativeVlanGuestNetwork(Network network) {
110+
return network.getTrafficType() == Networks.TrafficType.Guest && isNativeVlan(network.getBroadcastUri());
97111
}
98112

99113
private static boolean isVlanFilteringEnabled(long hostId, HostDetailsDao hostDetailsDao) {
@@ -102,12 +116,19 @@ private static boolean isVlanFilteringEnabled(long hostId, HostDetailsDao hostDe
102116
}
103117

104118
// a stopped Instance's nics carry no broadcast URI (released on stop), so fall back to the network's
105-
private static URI getEffectiveBroadcastUri(NicVO nic, NetworkDao networkDao) {
106-
if (nic.getBroadcastUri() != null) {
107-
return nic.getBroadcastUri();
119+
private static boolean isNativeVlanGuestNic(NicVO nic, NetworkDao networkDao) {
120+
if (nic.getMultiNetwork()) {
121+
return false;
122+
}
123+
URI nicBroadcastUri = nic.getBroadcastUri();
124+
if (nicBroadcastUri != null && !isNativeVlan(nicBroadcastUri)) {
125+
return false;
108126
}
109127
NetworkVO network = networkDao.findById(nic.getNetworkId());
110-
return network == null ? null : network.getBroadcastUri();
128+
if (network == null || network.getTrafficType() != Networks.TrafficType.Guest) {
129+
return false;
130+
}
131+
return isNativeVlan(nicBroadcastUri != null ? nicBroadcastUri : network.getBroadcastUri());
111132
}
112133

113134
private static boolean isNativeVlan(URI broadcastUri) {

‎core/src/test/java/com/cloud/agent/api/VlanTrunkMigrationHelperTest.java‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.Assert.assertTrue;
2323
import static org.mockito.ArgumentMatchers.any;
2424
import static org.mockito.ArgumentMatchers.eq;
25+
import static org.mockito.Mockito.lenient;
2526
import static org.mockito.Mockito.mock;
2627
import static org.mockito.Mockito.never;
2728
import static org.mockito.Mockito.times;
@@ -283,10 +284,20 @@ public void sendPostMigrationVlanTrunkMembershipIfNeededWarnsOnSendException() t
283284
// -- getVlanFilteringUnsuitableReason --
284285

285286
private NicVO nicOnVlan(int vlan, boolean multiNetwork) {
287+
return nicOnVlan(vlan, multiNetwork, Networks.TrafficType.Guest);
288+
}
289+
290+
private NicVO nicOnVlan(int vlan, boolean multiNetwork, Networks.TrafficType trafficType) {
286291
URI broadcastUri = Networks.BroadcastDomainType.Vlan.toUri(vlan);
287292
NicVO nic = mock(NicVO.class);
288293
when(nic.getBroadcastUri()).thenReturn(broadcastUri);
289294
when(nic.getMultiNetwork()).thenReturn(multiNetwork);
295+
if (!multiNetwork) {
296+
lenient().when(nic.getNetworkId()).thenReturn(5L);
297+
NetworkVO network = mock(NetworkVO.class);
298+
lenient().when(network.getTrafficType()).thenReturn(trafficType);
299+
lenient().when(networkDao.findById(5L)).thenReturn(network);
300+
}
290301
return nic;
291302
}
292303

@@ -340,6 +351,7 @@ public void getVlanFilteringUnsuitableReasonUsesNetworkVlanForStoppedNicWithoutB
340351
NetworkVO network = mock(NetworkVO.class);
341352
URI vlanOne = Networks.BroadcastDomainType.Vlan.toUri(1);
342353
when(network.getBroadcastUri()).thenReturn(vlanOne);
354+
when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
343355
when(networkDao.findById(5L)).thenReturn(network);
344356
givenHostVlanFiltering(true);
345357

@@ -351,10 +363,62 @@ public void getNativeVlanUnsuitableReasonRejectsVlanOneNetworkOnFilteringHostOnl
351363
Network network = mock(Network.class);
352364
URI vlanOne = Networks.BroadcastDomainType.Vlan.toUri(1);
353365
when(network.getBroadcastUri()).thenReturn(vlanOne);
366+
when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
354367
givenHostVlanFiltering(true);
355368
assertTrue(VlanTrunkMigrationHelper.getNativeVlanUnsuitableReason(network, DEST_HOST_ID, hostDetailsDao).contains("VLAN 1"));
356369

357370
givenHostVlanFiltering(false);
358371
assertNull(VlanTrunkMigrationHelper.getNativeVlanUnsuitableReason(network, DEST_HOST_ID, hostDetailsDao));
359372
}
373+
374+
@Test
375+
public void getVlanFilteringUnsuitableReasonIgnoresVlanOnePublicNic() {
376+
List<NicVO> nics = Collections.singletonList(nicOnVlan(1, false, Networks.TrafficType.Public));
377+
when(nicDao.listByVmId(VM_ID)).thenReturn(nics);
378+
379+
assertNull(VlanTrunkMigrationHelper.getVlanFilteringUnsuitableReason(VM_ID, DEST_HOST_ID, nicDao, networkDao, hostDetailsDao));
380+
}
381+
382+
@Test
383+
public void getNativeVlanUnsuitableReasonIgnoresVlanOneNonGuestNetwork() {
384+
Network network = mock(Network.class);
385+
when(network.getTrafficType()).thenReturn(Networks.TrafficType.Public);
386+
387+
assertNull(VlanTrunkMigrationHelper.getNativeVlanUnsuitableReason(network, DEST_HOST_ID, hostDetailsDao));
388+
}
389+
390+
@Test
391+
public void hasNativeVlanGuestNicIsTrueOnlyForGuestVlanOne() {
392+
assertTrue(VlanTrunkMigrationHelper.hasNativeVlanGuestNic(Collections.singletonList(nicOnVlan(1, false)), networkDao));
393+
assertFalse(VlanTrunkMigrationHelper.hasNativeVlanGuestNic(Collections.singletonList(nicOnVlan(100, false)), networkDao));
394+
}
395+
396+
@Test
397+
public void isNativeVlanGuestNetworkIsTrueOnlyForGuestVlanOne() {
398+
Network guestVlanOne = mock(Network.class);
399+
when(guestVlanOne.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
400+
when(guestVlanOne.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.Vlan.toUri(1));
401+
Network guestOtherVlan = mock(Network.class);
402+
when(guestOtherVlan.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
403+
when(guestOtherVlan.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.Vlan.toUri(100));
404+
405+
assertTrue(VlanTrunkMigrationHelper.isNativeVlanGuestNetwork(guestVlanOne));
406+
assertFalse(VlanTrunkMigrationHelper.isNativeVlanGuestNetwork(guestOtherVlan));
407+
}
408+
409+
@Test
410+
public void hasTrunkNicDetectsMultiNetworkNic() {
411+
assertTrue(VlanTrunkMigrationHelper.hasTrunkNic(Collections.singletonList(nicOnVlan(100, true))));
412+
assertFalse(VlanTrunkMigrationHelper.hasTrunkNic(Collections.singletonList(nicOnVlan(100, false))));
413+
}
414+
415+
@Test
416+
public void getVlanFilteringUnsuitableReasonDoesNotLookUpNetworksOrHostForNicsOnOtherVlans() {
417+
List<NicVO> nics = Collections.singletonList(nicOnVlan(100, false));
418+
when(nicDao.listByVmId(VM_ID)).thenReturn(nics);
419+
420+
assertNull(VlanTrunkMigrationHelper.getVlanFilteringUnsuitableReason(VM_ID, DEST_HOST_ID, nicDao, networkDao, hostDetailsDao));
421+
verify(networkDao, never()).findById(any(Long.class));
422+
verify(hostDetailsDao, never()).findDetail(any(Long.class), any(String.class));
423+
}
360424
}

‎server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import com.cloud.agent.api.Command;
7474
import com.cloud.agent.api.StartupCommand;
7575
import com.cloud.agent.api.StartupRoutingCommand;
76+
import com.cloud.agent.api.VlanTrunkMigrationHelper;
7677
import com.cloud.agent.manager.allocator.HostAllocator;
7778
import com.cloud.capacity.CapacityManager;
7879
import com.cloud.capacity.dao.CapacityDao;
@@ -151,6 +152,7 @@
151152
import com.cloud.vm.VirtualMachine.Event;
152153
import com.cloud.vm.VirtualMachine.State;
153154
import com.cloud.vm.VirtualMachineProfile;
155+
import com.cloud.network.dao.NetworkDao;
154156
import com.cloud.vm.NicVO;
155157
import com.cloud.vm.dao.NicDao;
156158
import com.cloud.vm.dao.UserVmDao;
@@ -192,6 +194,8 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy
192194
private VMTemplateDao templateDao;
193195
@Inject
194196
protected NicDao _nicDao;
197+
@Inject
198+
protected NetworkDao _networkDao;
195199

196200
private static final long ADMIN_ACCOUNT_ROLE_ID = 1l;
197201
private static final long INITIAL_RESERVATION_RELEASE_CHECKER_DELAY = 30L * 1000L; // thirty seconds expressed in milliseconds
@@ -484,22 +488,27 @@ private void avoidDifferentArchResources(VirtualMachineProfile vmProfile, DataCe
484488
*/
485489
@Override
486490
public void avoidHostsNotReadyForMultiNetworkNics(VirtualMachineProfile vmProfile, DataCenter dc, ExcludeList avoids) {
487-
boolean hasMultiNetworkNic = _nicDao.listByVmId(vmProfile.getId()).stream().anyMatch(NicVO::getMultiNetwork);
488-
if (!hasMultiNetworkNic) {
491+
List<NicVO> nics = _nicDao.listByVmId(vmProfile.getId());
492+
boolean needsFilteringHost = VlanTrunkMigrationHelper.hasTrunkNic(nics);
493+
boolean needsLegacyHost = !needsFilteringHost && VlanTrunkMigrationHelper.hasNativeVlanGuestNic(nics, _networkDao);
494+
if (!needsFilteringHost && !needsLegacyHost) {
495+
return;
496+
}
497+
Set<Long> filteringHostIds = _hostDetailsDao.findHostIdsWithVlanFilteringEnabled();
498+
// without VLAN-filtering hosts a VLAN 1 nic is placeable anywhere
499+
if (needsLegacyHost && filteringHostIds.isEmpty()) {
489500
return;
490501
}
491502
List<Long> candidateHostIds = _hostDao.listEnabledIdsByDataCenterId(dc.getId());
492503
if (CollectionUtils.isEmpty(candidateHostIds)) {
493504
return;
494505
}
495-
Set<Long> readyHostIds = _hostDetailsDao.findHostIdsWithVlanFilteringEnabled();
496-
List<Long> notReadyHostIds = candidateHostIds.stream()
497-
.filter(hostId -> !readyHostIds.contains(hostId))
506+
List<Long> unsuitableHostIds = candidateHostIds.stream()
507+
.filter(hostId -> filteringHostIds.contains(hostId) ? needsLegacyHost : needsFilteringHost)
498508
.collect(Collectors.toList());
499-
if (!notReadyHostIds.isEmpty()) {
500-
logger.debug("Excluding {} host(s) without VLAN filtering enabled, since VM [{}] has a multi-VLAN trunk nic",
501-
notReadyHostIds.size(), vmProfile);
502-
avoids.addHostList(notReadyHostIds);
509+
if (!unsuitableHostIds.isEmpty()) {
510+
logger.debug("Excluding {} host(s) unsuitable for the multi-VLAN trunk or VLAN 1 nics of VM [{}]", unsuitableHostIds.size(), vmProfile);
511+
avoids.addHostList(unsuitableHostIds);
503512
}
504513
}
505514

‎server/src/main/java/com/cloud/network/NetworkServiceImpl.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.util.Set;
4040
import java.util.TreeSet;
4141
import java.util.UUID;
42+
import java.util.stream.Collectors;
4243

4344
import javax.inject.Inject;
4445
import javax.naming.ConfigurationException;
@@ -1082,6 +1083,7 @@ private List<NetworkVO> associateNetworksInternal(NicVO nic, VirtualMachine vm,
10821083

10831084
validateTrunkSet(primaryNetwork, resultingSet);
10841085
resultingSet.forEach(this::validateNotNativeVlan);
1086+
validateNoNativeVlanOnOtherNics(vm, nic);
10851087
validateNoOverlappingCidrs(resultingSet);
10861088
// defer implement to the Instance's next start when stopped, like its primary network; a running Instance needs it now
10871089
boolean vmRunning = vm.getState() == VirtualMachine.State.Running;
@@ -1685,6 +1687,15 @@ private void validateNoDuplicateVlan(List<NetworkVO> resultingSet, Set<Long> jus
16851687
}
16861688
}
16871689

1690+
// A trunk nic needs a VLAN-filtering host and a Guest VLAN 1 nic a legacy one, so the Instance could start nowhere.
1691+
private void validateNoNativeVlanOnOtherNics(VirtualMachine vm, NicVO nic) {
1692+
List<NicVO> otherNics = _nicDao.listByVmId(vm.getId()).stream().filter(other -> other.getId() != nic.getId()).collect(Collectors.toList());
1693+
if (VlanTrunkMigrationHelper.hasNativeVlanGuestNic(otherNics, _networksDao)) {
1694+
throw new InvalidParameterValueException(String.format(
1695+
"Instance %s has a nic on a VLAN 1 network, which cannot be combined with a trunk nic", vm.getUuid()));
1696+
}
1697+
}
1698+
16881699
// VLAN 1 is the host uplink's PVID (egress untagged), so it is not isolated from untagged networks
16891700
private void validateNotNativeVlan(NetworkVO network) {
16901701
URI broadcastUri = network.getBroadcastUri();

‎server/src/main/java/com/cloud/vm/UserVmManagerImpl.java‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,7 @@ public UserVm addNicToVirtualMachine(AddNicToVMCmd cmd) throws InvalidParameterV
15761576
}
15771577

15781578
failIfNativeVlanOnVlanFilteringHost(vmInstance, network);
1579+
failIfNativeVlanOnVmWithTrunkNic(vmInstance, network);
15791580

15801581
setNicAsDefaultIfNeeded(vmInstance, profile);
15811582

@@ -6811,6 +6812,7 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE
68116812
throw new InvalidParameterValueException("Unable to use template " + templateId);
68126813
}
68136814
verifyTemplate(cmd, template, serviceOfferingId);
6815+
validateNoNativeVlanNicWithTrunkNic(cmd);
68146816

68156817
Long diskOfferingId = cmd.getDiskOfferingId();
68166818
DiskOffering diskOffering = null;
@@ -7863,6 +7865,28 @@ private void failIfNativeVlanOnVlanFilteringHost(UserVmVO vm, Network network) {
78637865
}
78647866
}
78657867

7868+
// A trunk nic needs a VLAN-filtering host and a Guest VLAN 1 nic a legacy one, so the Instance could start nowhere.
7869+
private void failIfNativeVlanOnVmWithTrunkNic(UserVmVO vm, Network network) {
7870+
if (VlanTrunkMigrationHelper.isNativeVlanGuestNetwork(network) && VlanTrunkMigrationHelper.hasTrunkNic(_nicDao.listByVmId(vm.getId()))) {
7871+
throw new InvalidParameterValueException(String.format("Cannot add network %s to Instance %s: it is on VLAN 1, the native VLAN of the host uplink, "
7872+
+ "and the Instance has a trunk nic", network.getUuid(), vm.getUuid()));
7873+
}
7874+
}
7875+
7876+
private void validateNoNativeVlanNicWithTrunkNic(BaseDeployVMCmd cmd) {
7877+
List<BaseDeployVMCmd.NicNetworkGrouping> groupings = cmd.getNicNetworksList();
7878+
if (groupings.stream().allMatch(grouping -> grouping.getAssociatedNetworkIds().isEmpty())) {
7879+
return;
7880+
}
7881+
for (BaseDeployVMCmd.NicNetworkGrouping grouping : groupings) {
7882+
Network primaryNetwork = _networkDao.findById(grouping.getPrimaryNetworkId());
7883+
if (primaryNetwork != null && VlanTrunkMigrationHelper.isNativeVlanGuestNetwork(primaryNetwork)) {
7884+
throw new InvalidParameterValueException(String.format("Network %s is on VLAN 1, the native VLAN of the host uplink, "
7885+
+ "and cannot be on an Instance that has a trunk nic", primaryNetwork.getUuid()));
7886+
}
7887+
}
7888+
}
7889+
78667890
private String getVlanFilteringUnsuitableReason(VMInstanceVO vm, Host host) {
78677891
return VlanTrunkMigrationHelper.getVlanFilteringUnsuitableReason(vm.getId(), host.getId(), _nicDao, _networkDao, _hostDetailsDao);
78687892
}
@@ -10290,6 +10314,7 @@ public UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws Insufficien
1029010314
}
1029110315
}
1029210316
verifyTemplate(cmd, template, serviceOffering.getId());
10317+
validateNoNativeVlanNicWithTrunkNic(cmd);
1029310318

1029410319
Long size = cmd.getSize();
1029510320

0 commit comments

Comments
 (0)