diff --git a/plugins/network-elements/ovs/src/main/java/com/cloud/network/ovs/OvsTunnelManagerImpl.java b/plugins/network-elements/ovs/src/main/java/com/cloud/network/ovs/OvsTunnelManagerImpl.java index 804f29c01b1e..7ce1df8d597a 100644 --- a/plugins/network-elements/ovs/src/main/java/com/cloud/network/ovs/OvsTunnelManagerImpl.java +++ b/plugins/network-elements/ovs/src/main/java/com/cloud/network/ovs/OvsTunnelManagerImpl.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -93,6 +94,11 @@ @Component public class OvsTunnelManagerImpl extends ManagerBase implements OvsTunnelManager, StateListener { + private static final long MIN_GRE_KEY = 0L; + private static final long MAX_GRE_KEY = 4294967295L; + private static final Set VPC_TOPOLOGY_NETWORK_STATES = Set.of( + Network.State.Setup, Network.State.Implementing, Network.State.Implemented); + // boolean _isEnabled; ScheduledExecutorService _executorPool; ScheduledExecutorService _cleanupExecutor; @@ -396,6 +402,12 @@ boolean isVpcEnabledForDistributedRouter(long vpcId) { return vpc.usesDistributedRouter(); } + boolean isOvsDistributedRouterVpc(long vpcId) { + VpcVO vpc = _vpcDao.findById(vpcId); + return vpc != null && vpc.usesDistributedRouter() + && _vpcMgr.isProviderSupportServiceInVpc(vpcId, Network.Service.Connectivity, Network.Provider.Ovs); + } + @Override public void checkAndPrepareHostForTunnelNetwork(Network nw, Host host) { if (nw.getVpcId() != null && isVpcEnabledForDistributedRouter(nw.getVpcId())) { @@ -684,25 +696,28 @@ private void handleVmStateChange(VMInstanceVO vm) { } for (Long vpcId: vpcIds) { - VpcVO vpc = _vpcDao.findById(vpcId); - // nothing to do if the VPC is not setup for distributed routing - if (vpc == null || !vpc.usesDistributedRouter()) { - return; - } + try { + if (!isOvsDistributedRouterVpc(vpcId)) { + continue; + } - // get the list of hosts on which VPC spans (i.e hosts that need to be aware of VPC topology change update) - List vpcSpannedHostIds = _ovsNetworkToplogyGuru.getVpcSpannedHosts(vpcId); - String bridgeName=generateBridgeNameForVpc(vpcId); + // get the list of hosts on which VPC spans (i.e hosts that need to be aware of VPC topology change update) + List vpcSpannedHostIds = _ovsNetworkToplogyGuru.getVpcSpannedHosts(vpcId); + String bridgeName=generateBridgeNameForVpc(vpcId); - OvsVpcPhysicalTopologyConfigCommand topologyConfigCommand = prepareVpcTopologyUpdate(vpcId); - topologyConfigCommand.setSequenceNumber(getNextTopologyUpdateSequenceNumber(vpcId)); + OvsVpcPhysicalTopologyConfigCommand topologyConfigCommand = prepareVpcTopologyUpdate(vpcId); + topologyConfigCommand.setSequenceNumber(getNextTopologyUpdateSequenceNumber(vpcId)); - // send topology change update to VPC spanned hosts - for (Long id: vpcSpannedHostIds) { - if (!sendVpcTopologyChangeUpdate(topologyConfigCommand, id, bridgeName)) { - logger.debug("Failed to send VPC topology change update to host : " + id + ". Moving on " + - "with rest of the host update."); + // send topology change update to VPC spanned hosts + for (Long id: vpcSpannedHostIds) { + if (!sendVpcTopologyChangeUpdate(topologyConfigCommand, id, bridgeName)) { + logger.debug("Failed to send VPC topology change update to host : " + id + ". Moving on " + + "with rest of the host update."); + } } + } catch (RuntimeException e) { + logger.error("Failed to update OVS distributed-router topology for VPC {} after VM {} changed state", + vpcId, vm.getId(), e); } } } @@ -731,6 +746,12 @@ OvsVpcPhysicalTopologyConfigCommand prepareVpcTopologyUpdate(long vpcId) { assert (vpc != null): "invalid vpc id"; List vpcNetworks = _vpcMgr.getVpcNetworks(vpcId); + List topologyNetworks = new ArrayList<>(); + for (Network network : vpcNetworks) { + if (VPC_TOPOLOGY_NETWORK_STATES.contains(network.getState())) { + topologyNetworks.add(network); + } + } List hostIds = _ovsNetworkToplogyGuru.getVpcSpannedHosts(vpcId); List vmIds = _ovsNetworkToplogyGuru.getAllActiveVmsInVpc(vpcId); @@ -741,7 +762,7 @@ OvsVpcPhysicalTopologyConfigCommand prepareVpcTopologyUpdate(long vpcId) { for (Long hostId : hostIds) { HostVO hostDetails = _hostDao.findById(hostId); String remoteIp = null; - for (Network network: vpcNetworks) { + for (Network network: topologyNetworks) { try { remoteIp = getGreEndpointIP(hostDetails, network); } catch (Exception e) { @@ -753,21 +774,39 @@ OvsVpcPhysicalTopologyConfigCommand prepareVpcTopologyUpdate(long vpcId) { hosts.add(host); } - for (Network network: vpcNetworks) { + for (Network network: topologyNetworks) { + if (network.getBroadcastDomainType() != BroadcastDomainType.Vswitch || network.getBroadcastUri() == null) { + throw new CloudRuntimeException(String.format( + "OVS distributed-router VPC %s contains network %s without a Vswitch broadcast URI", + vpc.getUuid(), network.getUuid())); + } String key = network.getBroadcastUri().getAuthority(); - long gre_key; - if (key.contains(".")) { - String[] parts = key.split("\\."); - gre_key = Long.parseLong(parts[1]); - } else { - try { - gre_key = Long.parseLong(BroadcastDomainType.getValue(key)); - } catch (Exception e) { - return null; - } + String expectedPrefix = vpcId + "."; + if (key == null || !key.startsWith(expectedPrefix) || key.indexOf('.', expectedPrefix.length()) >= 0) { + throw new CloudRuntimeException(String.format( + "OVS distributed-router network %s has invalid broadcast key %s for VPC %s", + network.getUuid(), key, vpc.getUuid())); + } + String greKeyValue = key.substring(expectedPrefix.length()); + long greKey; + try { + greKey = Long.parseLong(greKeyValue); + } catch (NumberFormatException e) { + throw new CloudRuntimeException(String.format( + "OVS distributed-router network %s has non-numeric GRE key %s", + network.getUuid(), greKeyValue), e); + } + if (greKey < MIN_GRE_KEY || greKey > MAX_GRE_KEY) { + throw new CloudRuntimeException(String.format( + "OVS distributed-router network %s has GRE key %s outside the supported range %s-%s", + network.getUuid(), greKeyValue, MIN_GRE_KEY, MAX_GRE_KEY)); } NicVO nic = _nicDao.findByIp4AddressAndNetworkId(network.getGateway(), network.getId()); - OvsVpcPhysicalTopologyConfigCommand.Tier tier = new OvsVpcPhysicalTopologyConfigCommand.Tier(gre_key, + if (nic == null) { + throw new CloudRuntimeException(String.format( + "Unable to find the gateway NIC for OVS distributed-router network %s", network.getUuid())); + } + OvsVpcPhysicalTopologyConfigCommand.Tier tier = new OvsVpcPhysicalTopologyConfigCommand.Tier(greKey, network.getUuid(), network.getGateway(), nic.getMacAddress(), network.getCidr()); tiers.add(tier); } @@ -802,9 +841,9 @@ public class NetworkAclEventsSubscriber implements MessageSubscriber { public void onPublishMessage(String senderAddress, String subject, Object args) { try { NetworkVO network = (NetworkVO) args; - String bridgeName=generateBridgeNameForVpc(network.getVpcId()); - if (network.getVpcId() != null && isVpcEnabledForDistributedRouter(network.getVpcId())) { - long vpcId = network.getVpcId(); + Long vpcId = network.getVpcId(); + if (vpcId != null && isOvsDistributedRouterVpc(vpcId)) { + String bridgeName = generateBridgeNameForVpc(vpcId); OvsVpcRoutingPolicyConfigCommand cmd = prepareVpcRoutingPolicyUpdate(vpcId); cmd.setSequenceNumber(getNextRoutingPolicyUpdateSequenceNumber(vpcId)); diff --git a/plugins/network-elements/ovs/src/test/java/com/cloud/network/ovs/OvsTunnelManagerImplTest.java b/plugins/network-elements/ovs/src/test/java/com/cloud/network/ovs/OvsTunnelManagerImplTest.java new file mode 100644 index 000000000000..be830ab4e331 --- /dev/null +++ b/plugins/network-elements/ovs/src/test/java/com/cloud/network/ovs/OvsTunnelManagerImplTest.java @@ -0,0 +1,513 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.network.ovs; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.OvsVpcPhysicalTopologyConfigCommand; +import com.cloud.host.dao.HostDao; +import com.cloud.network.Network; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.ovs.dao.VpcDistributedRouterSeqNoDao; +import com.cloud.network.ovs.dao.VpcDistributedRouterSeqNoVO; +import com.cloud.network.vpc.VpcManager; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.vm.NicVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; + +public class OvsTunnelManagerImplTest { + private static final long VPC_ID = 7L; + private static final long SECOND_VPC_ID = 8L; + + private OvsTunnelManagerImpl manager; + private VpcDao vpcDao; + private VpcManager vpcManager; + private OvsNetworkTopologyGuru topologyGuru; + private NicDao nicDao; + private VpcDistributedRouterSeqNoVO sequenceNumber; + + @Before + public void setUp() { + manager = new OvsTunnelManagerImpl(); + vpcDao = mock(VpcDao.class); + vpcManager = mock(VpcManager.class); + topologyGuru = mock(OvsNetworkTopologyGuru.class); + nicDao = mock(NicDao.class); + manager._vpcDao = vpcDao; + manager._vpcMgr = vpcManager; + manager._ovsNetworkToplogyGuru = topologyGuru; + manager._nicDao = nicDao; + manager._hostDao = mock(HostDao.class); + manager._vmInstanceDao = mock(VMInstanceDao.class); + manager._networkDao = mock(NetworkDao.class); + manager._vpcDrSeqNoDao = mock(VpcDistributedRouterSeqNoDao.class); + manager._agentMgr = mock(AgentManager.class); + } + + @Test + public void testIsOvsDistributedRouterVpcReturnsFalseWhenVpcIsMissing() { + assertFalse(manager.isOvsDistributedRouterVpc(VPC_ID)); + verify(vpcManager, never()).isProviderSupportServiceInVpc(anyLong(), + org.mockito.ArgumentMatchers.any(Network.Service.class), + org.mockito.ArgumentMatchers.any(Network.Provider.class)); + } + + @Test + public void testIsOvsDistributedRouterVpcReturnsFalseWhenVpcIsNotDistributed() { + VpcVO vpc = mock(VpcVO.class); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.usesDistributedRouter()).thenReturn(false); + + assertFalse(manager.isOvsDistributedRouterVpc(VPC_ID)); + } + + @Test + public void testIsOvsDistributedRouterVpcReturnsFalseForNsxDistributedVpc() { + VpcVO vpc = mock(VpcVO.class); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.usesDistributedRouter()).thenReturn(true); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(false); + + assertFalse(manager.isOvsDistributedRouterVpc(VPC_ID)); + } + + @Test + public void testIsOvsDistributedRouterVpcReturnsTrueForOvsConnectivityDistributedVpc() { + VpcVO vpc = mock(VpcVO.class); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.usesDistributedRouter()).thenReturn(true); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(true); + + assertTrue(manager.isOvsDistributedRouterVpc(VPC_ID)); + } + + @Test + public void testPostStateTransitionEventIgnoresNsxDistributedVpc() { + VpcVO vpc = mock(VpcVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + @SuppressWarnings("unchecked") + StateMachine2.Transition transition = mock(StateMachine2.Transition.class); + when(vm.getId()).thenReturn(11L); + when(topologyGuru.getVpcIdsVmIsPartOf(11L)).thenReturn(List.of(VPC_ID)); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.usesDistributedRouter()).thenReturn(true); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(false); + when(transition.getCurrentState()).thenReturn(VirtualMachine.State.Starting); + when(transition.getEvent()).thenReturn(VirtualMachine.Event.OperationSucceeded); + when(transition.getToState()).thenReturn(VirtualMachine.State.Running); + + assertTrue(manager.postStateTransitionEvent(transition, vm, true, null)); + + verify(topologyGuru, never()).getVpcSpannedHosts(anyLong()); + verify(vpcManager, never()).getVpcNetworks(anyLong()); + } + + @Test + public void testPostStateTransitionEventContinuesAfterNonOvsVpc() { + VpcVO firstVpc = mock(VpcVO.class); + VpcVO secondVpc = mock(VpcVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + @SuppressWarnings("unchecked") + StateMachine2.Transition transition = mock(StateMachine2.Transition.class); + when(vm.getId()).thenReturn(11L); + when(topologyGuru.getVpcIdsVmIsPartOf(11L)).thenReturn(List.of(VPC_ID, SECOND_VPC_ID)); + when(vpcDao.findById(VPC_ID)).thenReturn(firstVpc); + when(vpcDao.findById(SECOND_VPC_ID)).thenReturn(secondVpc); + when(firstVpc.usesDistributedRouter()).thenReturn(true); + when(secondVpc.usesDistributedRouter()).thenReturn(true); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(false); + when(vpcManager.isProviderSupportServiceInVpc(SECOND_VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(true); + when(secondVpc.getUuid()).thenReturn("second-vpc-uuid"); + when(secondVpc.getCidr()).thenReturn("10.1.0.0/16"); + when(transition.getCurrentState()).thenReturn(VirtualMachine.State.Starting); + when(transition.getEvent()).thenReturn(VirtualMachine.Event.OperationSucceeded); + when(transition.getToState()).thenReturn(VirtualMachine.State.Running); + when(topologyGuru.getVpcSpannedHosts(SECOND_VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(SECOND_VPC_ID)).thenReturn(Collections.emptyList()); + doReturn(Collections.emptyList()).when(vpcManager).getVpcNetworks(SECOND_VPC_ID); + prepareSequenceNumber(SECOND_VPC_ID); + + assertTrue(manager.postStateTransitionEvent(transition, vm, true, null)); + + verify(vpcManager).getVpcNetworks(SECOND_VPC_ID); + verify(manager._vpcDrSeqNoDao).update(1L, sequenceNumber); + } + + @Test + public void testPostStateTransitionEventContainsMalformedOvsTopologyAndContinues() { + VpcVO firstVpc = mock(VpcVO.class); + VpcVO secondVpc = mock(VpcVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + @SuppressWarnings("unchecked") + StateMachine2.Transition transition = mock(StateMachine2.Transition.class); + when(vm.getId()).thenReturn(11L); + when(topologyGuru.getVpcIdsVmIsPartOf(11L)).thenReturn(List.of(VPC_ID, SECOND_VPC_ID)); + when(vpcDao.findById(VPC_ID)).thenReturn(firstVpc); + when(vpcDao.findById(SECOND_VPC_ID)).thenReturn(secondVpc); + when(firstVpc.usesDistributedRouter()).thenReturn(true); + when(secondVpc.usesDistributedRouter()).thenReturn(true); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(true); + when(vpcManager.isProviderSupportServiceInVpc(SECOND_VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(true); + when(secondVpc.getUuid()).thenReturn("second-vpc-uuid"); + when(secondVpc.getCidr()).thenReturn("10.1.0.0/16"); + when(transition.getCurrentState()).thenReturn(VirtualMachine.State.Starting); + when(transition.getEvent()).thenReturn(VirtualMachine.Event.OperationSucceeded); + when(transition.getToState()).thenReturn(VirtualMachine.State.Running); + Network malformedNetwork = mock(Network.class); + when(topologyGuru.getVpcSpannedHosts(VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(VPC_ID)).thenReturn(Collections.emptyList()); + doReturn(List.of(malformedNetwork)).when(vpcManager).getVpcNetworks(VPC_ID); + when(firstVpc.getUuid()).thenReturn("vpc-uuid"); + when(firstVpc.getCidr()).thenReturn("10.0.0.0/16"); + when(malformedNetwork.getState()).thenReturn(Network.State.Implemented); + when(malformedNetwork.getUuid()).thenReturn("network-uuid"); + when(malformedNetwork.getBroadcastDomainType()).thenReturn(BroadcastDomainType.NSX); + when(topologyGuru.getVpcSpannedHosts(SECOND_VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(SECOND_VPC_ID)).thenReturn(Collections.emptyList()); + doReturn(Collections.emptyList()).when(vpcManager).getVpcNetworks(SECOND_VPC_ID); + prepareSequenceNumber(SECOND_VPC_ID); + + assertTrue(manager.postStateTransitionEvent(transition, vm, true, null)); + + verify(vpcManager).getVpcNetworks(SECOND_VPC_ID); + verify(manager._vpcDrSeqNoDao).update(1L, sequenceNumber); + } + + @Test + public void testPostStateTransitionEventContainsProviderLookupFailureAndProcessesLaterOvsVpc() { + VpcVO firstVpc = mock(VpcVO.class); + VpcVO secondVpc = mock(VpcVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + @SuppressWarnings("unchecked") + StateMachine2.Transition transition = mock(StateMachine2.Transition.class); + when(vm.getId()).thenReturn(11L); + when(topologyGuru.getVpcIdsVmIsPartOf(11L)).thenReturn(List.of(VPC_ID, SECOND_VPC_ID)); + when(vpcDao.findById(VPC_ID)).thenReturn(firstVpc); + when(vpcDao.findById(SECOND_VPC_ID)).thenReturn(secondVpc); + when(firstVpc.usesDistributedRouter()).thenReturn(true); + when(secondVpc.usesDistributedRouter()).thenReturn(true); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenThrow(new CloudRuntimeException("provider lookup failed")); + when(vpcManager.isProviderSupportServiceInVpc(SECOND_VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(true); + when(secondVpc.getUuid()).thenReturn("second-vpc-uuid"); + when(secondVpc.getCidr()).thenReturn("10.1.0.0/16"); + when(transition.getCurrentState()).thenReturn(VirtualMachine.State.Starting); + when(transition.getEvent()).thenReturn(VirtualMachine.Event.OperationSucceeded); + when(transition.getToState()).thenReturn(VirtualMachine.State.Running); + when(topologyGuru.getVpcSpannedHosts(SECOND_VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(SECOND_VPC_ID)).thenReturn(Collections.emptyList()); + doReturn(Collections.emptyList()).when(vpcManager).getVpcNetworks(SECOND_VPC_ID); + prepareSequenceNumber(SECOND_VPC_ID); + + assertTrue(manager.postStateTransitionEvent(transition, vm, true, null)); + + verify(vpcManager).getVpcNetworks(SECOND_VPC_ID); + verify(manager._vpcDrSeqNoDao).update(1L, sequenceNumber); + } + + @Test + public void testPostStateTransitionEventBuildsTopologyForOvsVpc() { + VpcVO vpc = mock(VpcVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + @SuppressWarnings("unchecked") + StateMachine2.Transition transition = mock(StateMachine2.Transition.class); + when(vm.getId()).thenReturn(11L); + when(topologyGuru.getVpcIdsVmIsPartOf(11L)).thenReturn(List.of(VPC_ID)); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.usesDistributedRouter()).thenReturn(true); + when(vpc.getUuid()).thenReturn("vpc-uuid"); + when(vpc.getCidr()).thenReturn("10.0.0.0/16"); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(true); + when(transition.getCurrentState()).thenReturn(VirtualMachine.State.Starting); + when(transition.getEvent()).thenReturn(VirtualMachine.Event.OperationSucceeded); + when(transition.getToState()).thenReturn(VirtualMachine.State.Running); + when(topologyGuru.getVpcSpannedHosts(VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(VPC_ID)).thenReturn(Collections.emptyList()); + doReturn(Collections.emptyList()).when(vpcManager).getVpcNetworks(VPC_ID); + prepareSequenceNumber(VPC_ID); + + assertTrue(manager.postStateTransitionEvent(transition, vm, true, null)); + + verify(vpcManager).getVpcNetworks(VPC_ID); + verify(manager._vpcDrSeqNoDao).update(1L, sequenceNumber); + } + + @Test + public void testNetworkAclSubscriberIgnoresNsxDistributedVpc() { + VpcVO vpc = mock(VpcVO.class); + NetworkVO network = mock(NetworkVO.class); + when(network.getVpcId()).thenReturn(VPC_ID); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.usesDistributedRouter()).thenReturn(true); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(false); + + manager.new NetworkAclEventsSubscriber().onPublishMessage("sender", "Network_ACL_Replaced", network); + + verify(topologyGuru, never()).getVpcSpannedHosts(anyLong()); + verify(vpcManager, never()).getVpcNetworks(anyLong()); + } + + @Test + public void testNetworkAclSubscriberBuildsPolicyForOvsVpc() { + VpcVO vpc = mock(VpcVO.class); + NetworkVO network = mock(NetworkVO.class); + when(network.getVpcId()).thenReturn(VPC_ID); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.usesDistributedRouter()).thenReturn(true); + when(vpc.getUuid()).thenReturn("vpc-uuid"); + when(vpc.getCidr()).thenReturn("10.0.0.0/16"); + when(vpcManager.isProviderSupportServiceInVpc(VPC_ID, Network.Service.Connectivity, Network.Provider.Ovs)) + .thenReturn(true); + doReturn(List.of(network)).when(vpcManager).getVpcNetworks(VPC_ID); + when(network.getNetworkACLId()).thenReturn(null); + when(topologyGuru.getVpcSpannedHosts(VPC_ID)).thenReturn(Collections.emptyList()); + prepareSequenceNumber(VPC_ID); + + manager.new NetworkAclEventsSubscriber().onPublishMessage("sender", "Network_ACL_Replaced", network); + + verify(vpcManager).getVpcNetworks(VPC_ID); + verify(manager._vpcDrSeqNoDao).update(1L, sequenceNumber); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsNonVswitchTier() { + VpcVO vpc = mock(VpcVO.class); + Network network = mock(Network.class); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.getUuid()).thenReturn("vpc-uuid"); + doReturn(List.of(network)).when(vpcManager).getVpcNetworks(VPC_ID); + when(topologyGuru.getVpcSpannedHosts(VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(VPC_ID)).thenReturn(Collections.emptyList()); + when(network.getState()).thenReturn(Network.State.Implemented); + when(network.getUuid()).thenReturn("network-uuid"); + when(network.getBroadcastDomainType()).thenReturn(BroadcastDomainType.NSX); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + } + + @Test + public void testPrepareVpcTopologyUpdateSkipsAllocatedTierWithoutBroadcastUri() { + VpcVO vpc = mock(VpcVO.class); + Network network = mock(Network.class); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.getCidr()).thenReturn("10.0.0.0/16"); + doReturn(List.of(network)).when(vpcManager).getVpcNetworks(VPC_ID); + when(topologyGuru.getVpcSpannedHosts(VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(VPC_ID)).thenReturn(Collections.emptyList()); + when(network.getState()).thenReturn(Network.State.Allocated); + + OvsVpcPhysicalTopologyConfigCommand command = manager.prepareVpcTopologyUpdate(VPC_ID); + + assertTrue(command.getVpcConfigInJson().contains("\"tiers\":[]")); + verify(network, never()).getBroadcastDomainType(); + verify(nicDao, never()).findByIp4AddressAndNetworkId( + org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.anyLong()); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsImplementedTierWithoutBroadcastUri() { + VpcVO vpc = mock(VpcVO.class); + Network network = mock(Network.class); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.getUuid()).thenReturn("vpc-uuid"); + doReturn(List.of(network)).when(vpcManager).getVpcNetworks(VPC_ID); + when(topologyGuru.getVpcSpannedHosts(VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(VPC_ID)).thenReturn(Collections.emptyList()); + when(network.getState()).thenReturn(Network.State.Implemented); + when(network.getUuid()).thenReturn("network-uuid"); + when(network.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Vswitch); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsBroadcastKeyForAnotherVpc() { + Network network = prepareVswitchNetwork("8.123"); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + + verify(nicDao, never()).findByIp4AddressAndNetworkId("10.0.1.1", 13L); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsNonNumericGreKey() { + prepareVswitchNetwork("7.invalid"); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsRepeatedDelimiterInBroadcastKey() { + prepareVswitchNetwork("7..123"); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsLeadingDelimiterInBroadcastKey() { + prepareVswitchNetwork(".7.123"); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsTrailingDelimiterInBroadcastKey() { + prepareVswitchNetwork("7.123."); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + } + + @Test + public void testPrepareVpcTopologyUpdateAcceptsGreKeyAboveSignedIntegerRange() { + prepareVswitchNetwork("7.2147483648"); + NicVO gatewayNic = prepareGatewayNic(); + + OvsVpcPhysicalTopologyConfigCommand command = manager.prepareVpcTopologyUpdate(VPC_ID); + + assertTrue(command.getVpcConfigInJson().contains("\"grekey\":2147483648")); + verify(gatewayNic).getMacAddress(); + } + + @Test + public void testPrepareVpcTopologyUpdateAcceptsMaximumUnsignedGreKey() { + prepareVswitchNetwork("7.4294967295"); + NicVO gatewayNic = prepareGatewayNic(); + + OvsVpcPhysicalTopologyConfigCommand command = manager.prepareVpcTopologyUpdate(VPC_ID); + + assertTrue(command.getVpcConfigInJson().contains("\"grekey\":4294967295")); + verify(gatewayNic).getMacAddress(); + } + + @Test + public void testPrepareVpcTopologyUpdateAcceptsZeroGreKey() { + prepareVswitchNetwork("7.0"); + NicVO gatewayNic = prepareGatewayNic(); + + OvsVpcPhysicalTopologyConfigCommand command = manager.prepareVpcTopologyUpdate(VPC_ID); + + assertTrue(command.getVpcConfigInJson().contains("\"grekey\":0")); + verify(gatewayNic).getMacAddress(); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsGreKeyAboveUnsignedRange() { + prepareVswitchNetwork("7.4294967296"); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + verify(nicDao, never()).findByIp4AddressAndNetworkId("10.0.1.1", 13L); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsNegativeGreKey() { + prepareVswitchNetwork("7.-1"); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + verify(nicDao, never()).findByIp4AddressAndNetworkId("10.0.1.1", 13L); + } + + @Test + public void testPrepareVpcTopologyUpdateRejectsMissingGatewayNic() { + prepareVswitchNetwork("7.123"); + + assertThrows(CloudRuntimeException.class, () -> manager.prepareVpcTopologyUpdate(VPC_ID)); + } + + @Test + public void testPrepareVpcTopologyUpdateBuildsValidOvsTopology() { + prepareVswitchNetwork("7.123"); + NicVO gatewayNic = mock(NicVO.class); + when(nicDao.findByIp4AddressAndNetworkId("10.0.1.1", 13L)).thenReturn(gatewayNic); + when(gatewayNic.getMacAddress()).thenReturn("02:00:00:00:00:01"); + + OvsVpcPhysicalTopologyConfigCommand command = manager.prepareVpcTopologyUpdate(VPC_ID); + + String topology = command.getVpcConfigInJson(); + assertTrue(topology.contains("\"grekey\":123")); + assertTrue(topology.contains("\"networkuuid\":\"network-uuid\"")); + assertTrue(topology.contains("\"gatewaymac\":\"02:00:00:00:00:01\"")); + } + + private Network prepareVswitchNetwork(String broadcastKey) { + VpcVO vpc = mock(VpcVO.class); + Network network = mock(Network.class); + when(vpcDao.findById(VPC_ID)).thenReturn(vpc); + when(vpc.getUuid()).thenReturn("vpc-uuid"); + when(vpc.getCidr()).thenReturn("10.0.0.0/16"); + doReturn(List.of(network)).when(vpcManager).getVpcNetworks(VPC_ID); + when(topologyGuru.getVpcSpannedHosts(VPC_ID)).thenReturn(Collections.emptyList()); + when(topologyGuru.getAllActiveVmsInVpc(VPC_ID)).thenReturn(Collections.emptyList()); + when(network.getId()).thenReturn(13L); + when(network.getUuid()).thenReturn("network-uuid"); + when(network.getGateway()).thenReturn("10.0.1.1"); + when(network.getCidr()).thenReturn("10.0.1.0/24"); + when(network.getState()).thenReturn(Network.State.Implemented); + when(network.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Vswitch); + when(network.getBroadcastUri()).thenReturn(BroadcastDomainType.Vswitch.toUri(broadcastKey)); + return network; + } + + private NicVO prepareGatewayNic() { + NicVO gatewayNic = mock(NicVO.class); + when(nicDao.findByIp4AddressAndNetworkId("10.0.1.1", 13L)).thenReturn(gatewayNic); + when(gatewayNic.getMacAddress()).thenReturn("02:00:00:00:00:01"); + return gatewayNic; + } + + private void prepareSequenceNumber(long vpcId) { + sequenceNumber = mock(VpcDistributedRouterSeqNoVO.class); + when(sequenceNumber.getId()).thenReturn(1L); + when(sequenceNumber.getTopologyUpdateSequenceNo()).thenReturn(1L); + when(sequenceNumber.getPolicyUpdateSequenceNo()).thenReturn(1L); + when(manager._vpcDrSeqNoDao.findByVpcId(vpcId)).thenReturn(sequenceNumber); + when(manager._vpcDrSeqNoDao.lockRow(1L, true)).thenReturn(sequenceNumber); + } +}