This is an automated email from the ASF dual-hosted git repository.
Pearl1594 pushed a commit to branch support-multi-vlan-guestnet
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/support-multi-vlan-guestnet by
this push:
new d644d6e808e 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
d644d6e808e is described below
commit d644d6e808ebd84fdae2216958ea89a5ca96f9dc
Author: Pearl1594 <[email protected]>
AuthorDate: Fri Oct 30 13:50:14 2026 -0400
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
---
.../cloud/agent/api/VlanTrunkMigrationHelper.java | 37 ++++++++++---
.../agent/api/VlanTrunkMigrationHelperTest.java | 64 ++++++++++++++++++++++
.../deploy/DeploymentPlanningManagerImpl.java | 27 ++++++---
.../java/com/cloud/network/NetworkServiceImpl.java | 11 ++++
.../main/java/com/cloud/vm/UserVmManagerImpl.java | 25 +++++++++
.../deploy/DeploymentPlanningManagerImplTest.java | 59 ++++++++++++++++++++
.../com/cloud/network/NetworkServiceImplTest.java | 33 +++++++++++
.../java/com/cloud/vm/UserVmManagerImplTest.java | 60 ++++++++++++++++++++
8 files changed, 299 insertions(+), 17 deletions(-)
diff --git
a/core/src/main/java/com/cloud/agent/api/VlanTrunkMigrationHelper.java
b/core/src/main/java/com/cloud/agent/api/VlanTrunkMigrationHelper.java
index ba4b528973f..ab5bf2afb92 100644
--- a/core/src/main/java/com/cloud/agent/api/VlanTrunkMigrationHelper.java
+++ b/core/src/main/java/com/cloud/agent/api/VlanTrunkMigrationHelper.java
@@ -17,6 +17,7 @@
package com.cloud.agent.api;
import java.net.URI;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -73,11 +74,11 @@ public class VlanTrunkMigrationHelper {
return nicDao.listByVmId(vmId).stream().anyMatch(nic ->
nicNeedsManualVlanMembership(nic, destVlanFilteringEnabled));
}
- // 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.
+ // 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.
public static String getVlanFilteringUnsuitableReason(long vmId, long
hostId, NicDao nicDao, NetworkDao networkDao, HostDetailsDao hostDetailsDao) {
List<NicVO> nics = nicDao.listByVmId(vmId);
- boolean hasTrunkNic = nics.stream().anyMatch(NicVO::getMultiNetwork);
- boolean hasNativeVlanNic = nics.stream().anyMatch(nic ->
!nic.getMultiNetwork() && isNativeVlan(getEffectiveBroadcastUri(nic,
networkDao)));
+ boolean hasTrunkNic = hasTrunkNic(nics);
+ boolean hasNativeVlanNic = hasNativeVlanGuestNic(nics, networkDao);
if (!hasTrunkNic && !hasNativeVlanNic) {
return null;
}
@@ -93,7 +94,20 @@ public class VlanTrunkMigrationHelper {
// For a network about to be plugged into a running Instance on this host.
public static String getNativeVlanUnsuitableReason(Network network, long
hostId, HostDetailsDao hostDetailsDao) {
- return isNativeVlan(network.getBroadcastUri()) &&
isVlanFilteringEnabled(hostId, hostDetailsDao) ?
NATIVE_VLAN_ON_FILTERING_HOST_REASON : null;
+ return isNativeVlanGuestNetwork(network) &&
isVlanFilteringEnabled(hostId, hostDetailsDao) ?
NATIVE_VLAN_ON_FILTERING_HOST_REASON : null;
+ }
+
+ public static boolean hasTrunkNic(Collection<NicVO> nics) {
+ return nics.stream().anyMatch(NicVO::getMultiNetwork);
+ }
+
+ // Only Guest nics ride the shared bridge; Public, Management and Storage
nics use their own bridges.
+ public static boolean hasNativeVlanGuestNic(Collection<NicVO> nics,
NetworkDao networkDao) {
+ return nics.stream().anyMatch(nic -> isNativeVlanGuestNic(nic,
networkDao));
+ }
+
+ public static boolean isNativeVlanGuestNetwork(Network network) {
+ return network.getTrafficType() == Networks.TrafficType.Guest &&
isNativeVlan(network.getBroadcastUri());
}
private static boolean isVlanFilteringEnabled(long hostId, HostDetailsDao
hostDetailsDao) {
@@ -102,12 +116,19 @@ public class VlanTrunkMigrationHelper {
}
// a stopped Instance's nics carry no broadcast URI (released on stop), so
fall back to the network's
- private static URI getEffectiveBroadcastUri(NicVO nic, NetworkDao
networkDao) {
- if (nic.getBroadcastUri() != null) {
- return nic.getBroadcastUri();
+ private static boolean isNativeVlanGuestNic(NicVO nic, NetworkDao
networkDao) {
+ if (nic.getMultiNetwork()) {
+ return false;
+ }
+ URI nicBroadcastUri = nic.getBroadcastUri();
+ if (nicBroadcastUri != null && !isNativeVlan(nicBroadcastUri)) {
+ return false;
}
NetworkVO network = networkDao.findById(nic.getNetworkId());
- return network == null ? null : network.getBroadcastUri();
+ if (network == null || network.getTrafficType() !=
Networks.TrafficType.Guest) {
+ return false;
+ }
+ return isNativeVlan(nicBroadcastUri != null ? nicBroadcastUri :
network.getBroadcastUri());
}
private static boolean isNativeVlan(URI broadcastUri) {
diff --git
a/core/src/test/java/com/cloud/agent/api/VlanTrunkMigrationHelperTest.java
b/core/src/test/java/com/cloud/agent/api/VlanTrunkMigrationHelperTest.java
index 0269e0d78ee..b28f83624cf 100644
--- a/core/src/test/java/com/cloud/agent/api/VlanTrunkMigrationHelperTest.java
+++ b/core/src/test/java/com/cloud/agent/api/VlanTrunkMigrationHelperTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -283,10 +284,20 @@ public class VlanTrunkMigrationHelperTest {
// -- getVlanFilteringUnsuitableReason --
private NicVO nicOnVlan(int vlan, boolean multiNetwork) {
+ return nicOnVlan(vlan, multiNetwork, Networks.TrafficType.Guest);
+ }
+
+ private NicVO nicOnVlan(int vlan, boolean multiNetwork,
Networks.TrafficType trafficType) {
URI broadcastUri = Networks.BroadcastDomainType.Vlan.toUri(vlan);
NicVO nic = mock(NicVO.class);
when(nic.getBroadcastUri()).thenReturn(broadcastUri);
when(nic.getMultiNetwork()).thenReturn(multiNetwork);
+ if (!multiNetwork) {
+ lenient().when(nic.getNetworkId()).thenReturn(5L);
+ NetworkVO network = mock(NetworkVO.class);
+ lenient().when(network.getTrafficType()).thenReturn(trafficType);
+ lenient().when(networkDao.findById(5L)).thenReturn(network);
+ }
return nic;
}
@@ -340,6 +351,7 @@ public class VlanTrunkMigrationHelperTest {
NetworkVO network = mock(NetworkVO.class);
URI vlanOne = Networks.BroadcastDomainType.Vlan.toUri(1);
when(network.getBroadcastUri()).thenReturn(vlanOne);
+ when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
when(networkDao.findById(5L)).thenReturn(network);
givenHostVlanFiltering(true);
@@ -351,10 +363,62 @@ public class VlanTrunkMigrationHelperTest {
Network network = mock(Network.class);
URI vlanOne = Networks.BroadcastDomainType.Vlan.toUri(1);
when(network.getBroadcastUri()).thenReturn(vlanOne);
+ when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
givenHostVlanFiltering(true);
assertTrue(VlanTrunkMigrationHelper.getNativeVlanUnsuitableReason(network,
DEST_HOST_ID, hostDetailsDao).contains("VLAN 1"));
givenHostVlanFiltering(false);
assertNull(VlanTrunkMigrationHelper.getNativeVlanUnsuitableReason(network,
DEST_HOST_ID, hostDetailsDao));
}
+
+ @Test
+ public void getVlanFilteringUnsuitableReasonIgnoresVlanOnePublicNic() {
+ List<NicVO> nics = Collections.singletonList(nicOnVlan(1, false,
Networks.TrafficType.Public));
+ when(nicDao.listByVmId(VM_ID)).thenReturn(nics);
+
+
assertNull(VlanTrunkMigrationHelper.getVlanFilteringUnsuitableReason(VM_ID,
DEST_HOST_ID, nicDao, networkDao, hostDetailsDao));
+ }
+
+ @Test
+ public void getNativeVlanUnsuitableReasonIgnoresVlanOneNonGuestNetwork() {
+ Network network = mock(Network.class);
+ when(network.getTrafficType()).thenReturn(Networks.TrafficType.Public);
+
+
assertNull(VlanTrunkMigrationHelper.getNativeVlanUnsuitableReason(network,
DEST_HOST_ID, hostDetailsDao));
+ }
+
+ @Test
+ public void hasNativeVlanGuestNicIsTrueOnlyForGuestVlanOne() {
+
assertTrue(VlanTrunkMigrationHelper.hasNativeVlanGuestNic(Collections.singletonList(nicOnVlan(1,
false)), networkDao));
+
assertFalse(VlanTrunkMigrationHelper.hasNativeVlanGuestNic(Collections.singletonList(nicOnVlan(100,
false)), networkDao));
+ }
+
+ @Test
+ public void isNativeVlanGuestNetworkIsTrueOnlyForGuestVlanOne() {
+ Network guestVlanOne = mock(Network.class);
+
when(guestVlanOne.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
+
when(guestVlanOne.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.Vlan.toUri(1));
+ Network guestOtherVlan = mock(Network.class);
+
when(guestOtherVlan.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
+
when(guestOtherVlan.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.Vlan.toUri(100));
+
+
assertTrue(VlanTrunkMigrationHelper.isNativeVlanGuestNetwork(guestVlanOne));
+
assertFalse(VlanTrunkMigrationHelper.isNativeVlanGuestNetwork(guestOtherVlan));
+ }
+
+ @Test
+ public void hasTrunkNicDetectsMultiNetworkNic() {
+
assertTrue(VlanTrunkMigrationHelper.hasTrunkNic(Collections.singletonList(nicOnVlan(100,
true))));
+
assertFalse(VlanTrunkMigrationHelper.hasTrunkNic(Collections.singletonList(nicOnVlan(100,
false))));
+ }
+
+ @Test
+ public void
getVlanFilteringUnsuitableReasonDoesNotLookUpNetworksOrHostForNicsOnOtherVlans()
{
+ List<NicVO> nics = Collections.singletonList(nicOnVlan(100, false));
+ when(nicDao.listByVmId(VM_ID)).thenReturn(nics);
+
+
assertNull(VlanTrunkMigrationHelper.getVlanFilteringUnsuitableReason(VM_ID,
DEST_HOST_ID, nicDao, networkDao, hostDetailsDao));
+ verify(networkDao, never()).findById(any(Long.class));
+ verify(hostDetailsDao, never()).findDetail(any(Long.class),
any(String.class));
+ }
}
diff --git
a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
index e158429c42c..b686f351adf 100644
--- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
+++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java
@@ -73,6 +73,7 @@ import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupRoutingCommand;
+import com.cloud.agent.api.VlanTrunkMigrationHelper;
import com.cloud.agent.manager.allocator.HostAllocator;
import com.cloud.capacity.CapacityManager;
import com.cloud.capacity.dao.CapacityDao;
@@ -151,6 +152,7 @@ import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.Event;
import com.cloud.vm.VirtualMachine.State;
import com.cloud.vm.VirtualMachineProfile;
+import com.cloud.network.dao.NetworkDao;
import com.cloud.vm.NicVO;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
@@ -192,6 +194,8 @@ StateListener<State, VirtualMachine.Event, VirtualMachine>,
Configurable {
private VMTemplateDao templateDao;
@Inject
protected NicDao _nicDao;
+ @Inject
+ protected NetworkDao _networkDao;
private static final long ADMIN_ACCOUNT_ROLE_ID = 1l;
private static final long INITIAL_RESERVATION_RELEASE_CHECKER_DELAY = 30L
* 1000L; // thirty seconds expressed in milliseconds
@@ -484,22 +488,27 @@ StateListener<State, VirtualMachine.Event,
VirtualMachine>, Configurable {
*/
@Override
public void avoidHostsNotReadyForMultiNetworkNics(VirtualMachineProfile
vmProfile, DataCenter dc, ExcludeList avoids) {
- boolean hasMultiNetworkNic =
_nicDao.listByVmId(vmProfile.getId()).stream().anyMatch(NicVO::getMultiNetwork);
- if (!hasMultiNetworkNic) {
+ List<NicVO> nics = _nicDao.listByVmId(vmProfile.getId());
+ boolean needsFilteringHost =
VlanTrunkMigrationHelper.hasTrunkNic(nics);
+ boolean needsLegacyHost = !needsFilteringHost &&
VlanTrunkMigrationHelper.hasNativeVlanGuestNic(nics, _networkDao);
+ if (!needsFilteringHost && !needsLegacyHost) {
+ return;
+ }
+ Set<Long> filteringHostIds =
_hostDetailsDao.findHostIdsWithVlanFilteringEnabled();
+ // without VLAN-filtering hosts a VLAN 1 nic is placeable anywhere
+ if (needsLegacyHost && filteringHostIds.isEmpty()) {
return;
}
List<Long> candidateHostIds =
_hostDao.listEnabledIdsByDataCenterId(dc.getId());
if (CollectionUtils.isEmpty(candidateHostIds)) {
return;
}
- Set<Long> readyHostIds =
_hostDetailsDao.findHostIdsWithVlanFilteringEnabled();
- List<Long> notReadyHostIds = candidateHostIds.stream()
- .filter(hostId -> !readyHostIds.contains(hostId))
+ List<Long> unsuitableHostIds = candidateHostIds.stream()
+ .filter(hostId -> filteringHostIds.contains(hostId) ?
needsLegacyHost : needsFilteringHost)
.collect(Collectors.toList());
- if (!notReadyHostIds.isEmpty()) {
- logger.debug("Excluding {} host(s) without VLAN filtering enabled,
since VM [{}] has a multi-VLAN trunk nic",
- notReadyHostIds.size(), vmProfile);
- avoids.addHostList(notReadyHostIds);
+ if (!unsuitableHostIds.isEmpty()) {
+ logger.debug("Excluding {} host(s) unsuitable for the multi-VLAN
trunk or VLAN 1 nics of VM [{}]", unsuitableHostIds.size(), vmProfile);
+ avoids.addHostList(unsuitableHostIds);
}
}
diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
index eb2bdb7daef..9ac33d47a34 100644
--- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
+++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java
@@ -39,6 +39,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
+import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
@@ -1082,6 +1083,7 @@ public class NetworkServiceImpl extends ManagerBase
implements NetworkService, C
validateTrunkSet(primaryNetwork, resultingSet);
resultingSet.forEach(this::validateNotNativeVlan);
+ validateNoNativeVlanOnOtherNics(vm, nic);
validateNoOverlappingCidrs(resultingSet);
// defer implement to the Instance's next start when stopped, like
its primary network; a running Instance needs it now
boolean vmRunning = vm.getState() == VirtualMachine.State.Running;
@@ -1685,6 +1687,15 @@ public class NetworkServiceImpl extends ManagerBase
implements NetworkService, C
}
}
+ // A trunk nic needs a VLAN-filtering host and a Guest VLAN 1 nic a legacy
one, so the Instance could start nowhere.
+ private void validateNoNativeVlanOnOtherNics(VirtualMachine vm, NicVO nic)
{
+ List<NicVO> otherNics =
_nicDao.listByVmId(vm.getId()).stream().filter(other -> other.getId() !=
nic.getId()).collect(Collectors.toList());
+ if (VlanTrunkMigrationHelper.hasNativeVlanGuestNic(otherNics,
_networksDao)) {
+ throw new InvalidParameterValueException(String.format(
+ "Instance %s has a nic on a VLAN 1 network, which cannot
be combined with a trunk nic", vm.getUuid()));
+ }
+ }
+
// VLAN 1 is the host uplink's PVID (egress untagged), so it is not
isolated from untagged networks
private void validateNotNativeVlan(NetworkVO network) {
URI broadcastUri = network.getBroadcastUri();
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index bc8e9420bdb..a0aeabf4dff 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -1576,6 +1576,7 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
failIfNativeVlanOnVlanFilteringHost(vmInstance, network);
+ failIfNativeVlanOnVmWithTrunkNic(vmInstance, network);
setNicAsDefaultIfNeeded(vmInstance, profile);
@@ -6811,6 +6812,7 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
throw new InvalidParameterValueException("Unable to use template "
+ templateId);
}
verifyTemplate(cmd, template, serviceOfferingId);
+ validateNoNativeVlanNicWithTrunkNic(cmd);
Long diskOfferingId = cmd.getDiskOfferingId();
DiskOffering diskOffering = null;
@@ -7863,6 +7865,28 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
}
+ // A trunk nic needs a VLAN-filtering host and a Guest VLAN 1 nic a legacy
one, so the Instance could start nowhere.
+ private void failIfNativeVlanOnVmWithTrunkNic(UserVmVO vm, Network
network) {
+ if (VlanTrunkMigrationHelper.isNativeVlanGuestNetwork(network) &&
VlanTrunkMigrationHelper.hasTrunkNic(_nicDao.listByVmId(vm.getId()))) {
+ throw new InvalidParameterValueException(String.format("Cannot add
network %s to Instance %s: it is on VLAN 1, the native VLAN of the host uplink,
"
+ + "and the Instance has a trunk nic", network.getUuid(),
vm.getUuid()));
+ }
+ }
+
+ private void validateNoNativeVlanNicWithTrunkNic(BaseDeployVMCmd cmd) {
+ List<BaseDeployVMCmd.NicNetworkGrouping> groupings =
cmd.getNicNetworksList();
+ if (groupings.stream().allMatch(grouping ->
grouping.getAssociatedNetworkIds().isEmpty())) {
+ return;
+ }
+ for (BaseDeployVMCmd.NicNetworkGrouping grouping : groupings) {
+ Network primaryNetwork =
_networkDao.findById(grouping.getPrimaryNetworkId());
+ if (primaryNetwork != null &&
VlanTrunkMigrationHelper.isNativeVlanGuestNetwork(primaryNetwork)) {
+ throw new
InvalidParameterValueException(String.format("Network %s is on VLAN 1, the
native VLAN of the host uplink, "
+ + "and cannot be on an Instance that has a trunk nic",
primaryNetwork.getUuid()));
+ }
+ }
+ }
+
private String getVlanFilteringUnsuitableReason(VMInstanceVO vm, Host
host) {
return
VlanTrunkMigrationHelper.getVlanFilteringUnsuitableReason(vm.getId(),
host.getId(), _nicDao, _networkDao, _hostDetailsDao);
}
@@ -10290,6 +10314,7 @@ public class UserVmManagerImpl extends ManagerBase
implements UserVmManager, Vir
}
}
verifyTemplate(cmd, template, serviceOffering.getId());
+ validateNoNativeVlanNicWithTrunkNic(cmd);
Long size = cmd.getSize();
diff --git
a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java
b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java
index e7f59eadeb8..978cbfd98af 100644
---
a/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java
+++
b/server/src/test/java/com/cloud/deploy/DeploymentPlanningManagerImplTest.java
@@ -80,6 +80,9 @@ import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.Type;
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.VirtualMachineProfileImpl;
+import com.cloud.network.Networks;
+import com.cloud.network.dao.NetworkDao;
+import com.cloud.network.dao.NetworkVO;
import com.cloud.vm.NicVO;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
@@ -234,6 +237,9 @@ public class DeploymentPlanningManagerImplTest {
@Mock
NicDao nicDao;
+ @Mock
+ NetworkDao networkDao;
+
@Inject
HostPodDao _podDao;
@@ -530,6 +536,54 @@ public class DeploymentPlanningManagerImplTest {
Assert.assertFalse(avoids.getHostsToAvoid().contains(readyHostId));
}
+ @Test
+ public void
avoidHostsNotReadyForMultiNetworkNicsExcludesFilteringHostsForGuestVlanOneNic()
{
+ VirtualMachineProfile vmProfile =
Mockito.mock(VirtualMachineProfile.class);
+ Mockito.when(vmProfile.getId()).thenReturn(instanceId);
+ DataCenter dc = Mockito.mock(DataCenter.class);
+ Mockito.when(dc.getId()).thenReturn(dataCenterId);
+ ExcludeList avoids = new ExcludeList();
+
+ NicVO vlanOneNic = Mockito.mock(NicVO.class);
+ Mockito.when(vlanOneNic.getMultiNetwork()).thenReturn(false);
+ Mockito.when(vlanOneNic.getNetworkId()).thenReturn(5L);
+
Mockito.when(vlanOneNic.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.Vlan.toUri(1));
+
Mockito.when(nicDao.listByVmId(instanceId)).thenReturn(Arrays.asList(vlanOneNic));
+ NetworkVO network = Mockito.mock(NetworkVO.class);
+
Mockito.when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
+ Mockito.when(networkDao.findById(5L)).thenReturn(network);
+
+ long filteringHostId = 10L;
+ long legacyHostId = 11L;
+
Mockito.when(hostDao.listEnabledIdsByDataCenterId(dataCenterId)).thenReturn(Arrays.asList(filteringHostId,
legacyHostId));
+
Mockito.when(hostDetailsDao.findHostIdsWithVlanFilteringEnabled()).thenReturn(Collections.singleton(filteringHostId));
+
+ _dpm.avoidHostsNotReadyForMultiNetworkNics(vmProfile, dc, avoids);
+
+ Assert.assertEquals(1, avoids.getHostsToAvoid().size());
+ Assert.assertTrue(avoids.getHostsToAvoid().contains(filteringHostId));
+ }
+
+ @Test
+ public void
avoidHostsNotReadyForMultiNetworkNicsDoesNoLookupsForNicsOnOtherVlans() {
+ VirtualMachineProfile vmProfile =
Mockito.mock(VirtualMachineProfile.class);
+ Mockito.when(vmProfile.getId()).thenReturn(instanceId);
+ DataCenter dc = Mockito.mock(DataCenter.class);
+ ExcludeList avoids = new ExcludeList();
+ NicVO nic = Mockito.mock(NicVO.class);
+ Mockito.when(nic.getMultiNetwork()).thenReturn(false);
+
Mockito.when(nic.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.Vlan.toUri(100));
+
Mockito.when(nicDao.listByVmId(instanceId)).thenReturn(Arrays.asList(nic));
+ Mockito.clearInvocations(hostDao, hostDetailsDao);
+
+ _dpm.avoidHostsNotReadyForMultiNetworkNics(vmProfile, dc, avoids);
+
+ assertAvoidIsEmpty(avoids, true, true, true, true);
+ Mockito.verify(networkDao,
Mockito.never()).findById(Mockito.anyLong());
+ Mockito.verify(hostDetailsDao,
Mockito.never()).findHostIdsWithVlanFilteringEnabled();
+ Mockito.verify(hostDao,
Mockito.never()).listEnabledIdsByDataCenterId(Mockito.anyLong());
+ }
+
@Test
public void avoidDisabledPodsTestNoDisabledPod() {
DataCenter dc = Mockito.mock(DataCenter.class);
@@ -1097,6 +1151,11 @@ public class DeploymentPlanningManagerImplTest {
return Mockito.mock(NicDao.class);
}
+ @Bean
+ public NetworkDao networkDao() {
+ return Mockito.mock(NetworkDao.class);
+ }
+
@Bean
public ClusterDetailsDao clusterDetailsDao() {
diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java
b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java
index ae5b78348b1..22317e3e118 100644
--- a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java
+++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java
@@ -1545,6 +1545,39 @@ public class NetworkServiceImplTest {
ReflectionTestUtils.invokeMethod(service, "validateNotNativeVlan", new
NetworkVO());
}
+ private NicVO mockNicOnNetwork(long nicId, long networkId, String vlanUri,
Networks.TrafficType trafficType) throws Exception {
+ NicVO nic = mockNic(nicId, 5L, networkId, false);
+ NetworkVO network = new NetworkVO();
+ network.setBroadcastUri(new java.net.URI(vlanUri));
+ network.setTrafficType(trafficType);
+ Mockito.when(networkDao.findById(networkId)).thenReturn(network);
+ return nic;
+ }
+
+ @Test
+ public void validateNoNativeVlanOnOtherNicsRejectsAnotherGuestVlanOneNic()
throws Exception {
+ NicVO trunkNic = mockNic(11L, 5L, 205L, false);
+ NicVO vlanOneNic = mockNicOnNetwork(12L, 300L, "vlan://1",
Networks.TrafficType.Guest);
+ Mockito.when(nicDao.listByVmId(5L)).thenReturn(Arrays.asList(trunkNic,
vlanOneNic));
+ UserVmVO vm = mockVm(VirtualMachine.State.Stopped, null);
+ Mockito.when(vm.getId()).thenReturn(5L);
+
+ Assert.assertThrows(InvalidParameterValueException.class,
+ () -> ReflectionTestUtils.invokeMethod(service,
"validateNoNativeVlanOnOtherNics", vm, trunkNic));
+ }
+
+ @Test
+ public void
validateNoNativeVlanOnOtherNicsAllowsVlanOnePublicNicAndOtherVlans() throws
Exception {
+ NicVO trunkNic = mockNic(11L, 5L, 205L, false);
+ NicVO publicNic = mockNicOnNetwork(12L, 300L, "vlan://1",
Networks.TrafficType.Public);
+ NicVO otherVlanNic = mockNicOnNetwork(13L, 301L, "vlan://100",
Networks.TrafficType.Guest);
+ Mockito.when(nicDao.listByVmId(5L)).thenReturn(Arrays.asList(trunkNic,
publicNic, otherVlanNic));
+ UserVmVO vm = mockVm(VirtualMachine.State.Stopped, null);
+ Mockito.when(vm.getId()).thenReturn(5L);
+
+ ReflectionTestUtils.invokeMethod(service,
"validateNoNativeVlanOnOtherNics", vm, trunkNic);
+ }
+
@Test(expected = InvalidParameterValueException.class)
public void associateNetworkToNicFailsWhenNicNotFound() throws Exception {
Mockito.when(nicDao.findById(11L)).thenReturn(null);
diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
index 457a8c4afc1..b3d6c479347 100644
--- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
+++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java
@@ -151,6 +151,7 @@ import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostDetailsDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.network.Network;
+import com.cloud.network.Networks;
import com.cloud.network.NetworkModel;
import com.cloud.network.NetworkService;
import com.cloud.network.as.AutoScaleManager;
@@ -786,6 +787,65 @@ public class UserVmManagerImplTest {
Mockito.verify(userVmManagerImpl,
Mockito.never()).expunge(Mockito.any());
}
+ private Network mockNetworkOnVlan(int vlan, Networks.TrafficType
trafficType) {
+ Network network = Mockito.mock(Network.class);
+ Mockito.when(network.getTrafficType()).thenReturn(trafficType);
+
Mockito.when(network.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.Vlan.toUri(vlan));
+ return network;
+ }
+
+ @Test
+ public void
failIfNativeVlanOnVmWithTrunkNicRejectsGuestVlanOneNetworkWhenVmHasTrunkNic() {
+ UserVmVO vm = Mockito.mock(UserVmVO.class);
+ Mockito.when(vm.getId()).thenReturn(99L);
+ NicVO trunkNic = Mockito.mock(NicVO.class);
+ Mockito.when(trunkNic.getMultiNetwork()).thenReturn(true);
+
Mockito.when(nicDao.listByVmId(99L)).thenReturn(Collections.singletonList(trunkNic));
+ Network vlanOne = mockNetworkOnVlan(1, Networks.TrafficType.Guest);
+
+ Assert.assertThrows(InvalidParameterValueException.class,
+ () -> ReflectionTestUtils.invokeMethod(userVmManagerImpl,
"failIfNativeVlanOnVmWithTrunkNic", vm, vlanOne));
+ }
+
+ @Test
+ public void
failIfNativeVlanOnVmWithTrunkNicAllowsGuestVlanOneNetworkWithoutTrunkNic() {
+ UserVmVO vm = Mockito.mock(UserVmVO.class);
+ Mockito.when(vm.getId()).thenReturn(99L);
+ NicVO plainNic = Mockito.mock(NicVO.class);
+ Mockito.when(plainNic.getMultiNetwork()).thenReturn(false);
+
Mockito.when(nicDao.listByVmId(99L)).thenReturn(Collections.singletonList(plainNic));
+
+ ReflectionTestUtils.invokeMethod(userVmManagerImpl,
"failIfNativeVlanOnVmWithTrunkNic", vm, mockNetworkOnVlan(1,
Networks.TrafficType.Guest));
+ }
+
+ @Test
+ public void
validateNoNativeVlanNicWithTrunkNicRejectsVlanOnePrimaryAlongsideATrunkNic() {
+ BaseDeployVMCmd cmd = Mockito.mock(BaseDeployVMCmd.class);
+ BaseDeployVMCmd.NicNetworkGrouping trunkGrouping = new
BaseDeployVMCmd.NicNetworkGrouping(
+ Arrays.asList(206L, 207L), null, null, null,
Collections.emptyMap());
+ BaseDeployVMCmd.NicNetworkGrouping vlanOneGrouping = new
BaseDeployVMCmd.NicNetworkGrouping(
+ Collections.singletonList(208L), null, null, null,
Collections.emptyMap());
+
Mockito.when(cmd.getNicNetworksList()).thenReturn(Arrays.asList(trunkGrouping,
vlanOneGrouping));
+ NetworkVO vlanOne = Mockito.mock(NetworkVO.class);
+
Mockito.when(vlanOne.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
+
Mockito.when(vlanOne.getBroadcastUri()).thenReturn(Networks.BroadcastDomainType.Vlan.toUri(1));
+
Mockito.when(_networkDao.findById(206L)).thenReturn(Mockito.mock(NetworkVO.class));
+ Mockito.when(_networkDao.findById(208L)).thenReturn(vlanOne);
+
+ Assert.assertThrows(InvalidParameterValueException.class,
+ () -> ReflectionTestUtils.invokeMethod(userVmManagerImpl,
"validateNoNativeVlanNicWithTrunkNic", cmd));
+ }
+
+ @Test
+ public void
validateNoNativeVlanNicWithTrunkNicAllowsVlanOneNicWhenNoTrunkNicIsRequested() {
+ BaseDeployVMCmd cmd = Mockito.mock(BaseDeployVMCmd.class);
+ BaseDeployVMCmd.NicNetworkGrouping plainGrouping = new
BaseDeployVMCmd.NicNetworkGrouping(
+ Collections.singletonList(208L), null, null, null,
Collections.emptyMap());
+
Mockito.when(cmd.getNicNetworksList()).thenReturn(Collections.singletonList(plainGrouping));
+
+ ReflectionTestUtils.invokeMethod(userVmManagerImpl,
"validateNoNativeVlanNicWithTrunkNic", cmd);
+ }
+
private VMInstanceVO mockVmWithId(long id) {
VMInstanceVO vm = Mockito.mock(VMInstanceVO.class);
Mockito.when(vm.getId()).thenReturn(id);