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);

Reply via email to