diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java index 930f38d2aa0..1f2f4859384 100644 --- a/agent/src/main/java/com/cloud/agent/Agent.java +++ b/agent/src/main/java/com/cloud/agent/Agent.java @@ -811,7 +811,10 @@ public class Agent implements HandlerFactory, IAgentControl { public void processReadyCommand(final Command cmd) { final ReadyCommand ready = (ReadyCommand)cmd; // Set human readable sizes; - NumbersUtil.enableHumanReadableSizes = ready.getEnableHumanReadableSizes(); + Boolean humanReadable = ready.getEnableHumanReadableSizes(); + if (humanReadable != null){ + NumbersUtil.enableHumanReadableSizes = humanReadable; + } s_logger.info("Processing agent ready command, agent id = " + ready.getHostId()); if (ready.getHostId() != null) { diff --git a/api/src/main/java/com/cloud/agent/api/PvlanSetupCommand.java b/api/src/main/java/com/cloud/agent/api/PvlanSetupCommand.java index 512d1bcbe4a..dc571f0d52c 100644 --- a/api/src/main/java/com/cloud/agent/api/PvlanSetupCommand.java +++ b/api/src/main/java/com/cloud/agent/api/PvlanSetupCommand.java @@ -34,6 +34,7 @@ public class PvlanSetupCommand extends Command { private String dhcpIp; private Type type; private String networkTag; + private String pvlanType; protected PvlanSetupCommand() { } @@ -43,6 +44,7 @@ public class PvlanSetupCommand extends Command { this.op = op; this.primary = NetUtils.getPrimaryPvlanFromUri(uri); this.isolated = NetUtils.getIsolatedPvlanFromUri(uri); + this.pvlanType = NetUtils.getPvlanTypeFromUri(uri); this.networkTag = networkTag; } @@ -116,4 +118,8 @@ public class PvlanSetupCommand extends Command { public String getNetworkTag() { return networkTag; } + + public String getPvlanType() { + return pvlanType; + } } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index f05cbbcb26d..8f722c92fde 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -3756,6 +3756,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac try { result = plugNic(network, nicTO, vmTO, context, dest); if (result) { + _userVmMgr.setupVmForPvlan(true, vm.getHostId(), nic); s_logger.debug("Nic is plugged successfully for vm " + vm + " in network " + network + ". Vm is a part of network now"); final long isDefault = nic.isDefaultNic() ? 1 : 0; // insert nic's Id into DB as resource_name @@ -3863,6 +3864,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.debug("Un-plugging nic " + nic + " for vm " + vm + " from network " + network); final boolean result = unplugNic(network, nicTO, vmTO, context, dest); if (result) { + _userVmMgr.setupVmForPvlan(false, vm.getHostId(), nicProfile); s_logger.debug("Nic is unplugged successfully for vm " + vm + " in network " + network); final long isDefault = nic.isDefaultNic() ? 1 : 0; UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index e16bde62b3f..2c6dbbe55b8 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.engine.orchestration; - import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -215,6 +214,7 @@ import com.cloud.vm.NicProfile; import com.cloud.vm.NicVO; import com.cloud.vm.ReservationContext; import com.cloud.vm.ReservationContextImpl; +import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -299,6 +299,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra RemoteAccessVpnDao _remoteAccessVpnDao; @Inject VpcVirtualNetworkApplianceService _routerService; + @Inject + UserVmManager _userVmMgr; List networkGurus; @@ -1792,6 +1794,11 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra s_logger.error("NetworkGuru " + guru + " prepareForMigration failed."); // XXX: Transaction error } } + + if (network.getGuestType() == Network.GuestType.L2 && vm.getType() == VirtualMachine.Type.User) { + _userVmMgr.setupVmForPvlan(false, vm.getVirtualMachine().getHostId(), profile); + } + final List providersToImplement = getNetworkProviders(network.getId()); for (final NetworkElement element : networkElements) { if (providersToImplement.contains(element.getProvider())) { @@ -1912,6 +1919,11 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (guru instanceof NetworkMigrationResponder) { ((NetworkMigrationResponder)guru).commitMigration(nicSrc, network, src, src_context, dst_context); } + + if (network.getGuestType() == Network.GuestType.L2 && src.getType() == VirtualMachine.Type.User) { + _userVmMgr.setupVmForPvlan(true, src.getVirtualMachine().getHostId(), nicSrc); + } + final List providersToImplement = getNetworkProviders(network.getId()); for (final NetworkElement element : networkElements) { if (providersToImplement.contains(element.getProvider())) { @@ -1943,6 +1955,11 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (guru instanceof NetworkMigrationResponder) { ((NetworkMigrationResponder)guru).rollbackMigration(nicDst, network, dst, src_context, dst_context); } + + if (network.getGuestType() == Network.GuestType.L2 && src.getType() == VirtualMachine.Type.User) { + _userVmMgr.setupVmForPvlan(true, dst.getVirtualMachine().getHostId(), nicDst); + } + final List providersToImplement = getNetworkProviders(network.getId()); for (final NetworkElement element : networkElements) { if (providersToImplement.contains(element.getProvider())) { @@ -2498,6 +2515,12 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } else { uri = BroadcastDomainType.fromString(vlanIdFinal); } + + if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString()).size() > 0) { + throw new InvalidParameterValueException("Network with vlan " + vlanIdFinal + + " already exists or overlaps with other network pvlans in zone " + zoneId); + } + userNetwork.setBroadcastUri(uri); if (!vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) { userNetwork.setBroadcastDomainType(BroadcastDomainType.Vlan); @@ -2508,7 +2531,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) { throw new InvalidParameterValueException("Cannot support pvlan with untagged primary vlan!"); } - URI uri = NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan); + URI uri = NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan, isolatedPvlanType.toString()); if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString(), isolatedPvlanType).size() > 0) { throw new InvalidParameterValueException("Network with primary vlan " + vlanIdFinal + " and secondary vlan " + isolatedPvlan + " type " + isolatedPvlanType + diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java index a84e4d575f4..c82e4e74599 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java @@ -126,4 +126,6 @@ public interface NetworkDao extends GenericDao, StateDao listByAccountIdNetworkName(long accountId, String name); List listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType); + + List listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java index eeee3d12c65..2578a14fc70 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java @@ -740,6 +740,7 @@ public class NetworkDaoImpl extends GenericDaoBaseimplements Ne * - The requested exact PVLAN pair exists * - The requested secondary VLAN ID is secondary VLAN ID of an existing PVLAN pair * - The requested secondary VLAN ID is primary VLAN ID of an existing PVLAN pair + * - The requested primary VLAN ID is secondary VLAN ID of an existing PVLAN pair */ protected boolean isNetworkOverlappingRequestedPvlan(Integer existingPrimaryVlan, Integer existingSecondaryVlan, Network.PVlanType existingPvlanType, Integer requestedPrimaryVlan, Integer requestedSecondaryVlan, Network.PVlanType requestedPvlanType) { @@ -749,6 +750,7 @@ public class NetworkDaoImpl extends GenericDaoBaseimplements Ne } boolean exactMatch = existingPrimaryVlan.equals(requestedPrimaryVlan) && existingSecondaryVlan.equals(requestedSecondaryVlan); boolean secondaryVlanUsed = requestedPvlanType != Network.PVlanType.Promiscuous && requestedSecondaryVlan.equals(existingPrimaryVlan) || requestedSecondaryVlan.equals(existingSecondaryVlan); + boolean primaryVlanUsed = existingPvlanType != Network.PVlanType.Promiscuous && requestedPrimaryVlan.equals(existingSecondaryVlan); boolean isolatedMax = false; boolean promiscuousMax = false; if (requestedPvlanType == Network.PVlanType.Isolated && existingPrimaryVlan.equals(requestedPrimaryVlan) && existingPvlanType.equals(Network.PVlanType.Isolated)) { @@ -756,7 +758,12 @@ public class NetworkDaoImpl extends GenericDaoBaseimplements Ne } else if (requestedPvlanType == Network.PVlanType.Promiscuous && existingPrimaryVlan.equals(requestedPrimaryVlan) && existingPvlanType == Network.PVlanType.Promiscuous) { promiscuousMax = true; } - return exactMatch || secondaryVlanUsed || isolatedMax || promiscuousMax; + return exactMatch || secondaryVlanUsed || primaryVlanUsed || isolatedMax || promiscuousMax; + } + + // True when a VLAN ID overlaps with an existing PVLAN primary or secondary ID + protected boolean isNetworkOverlappingRequestedPvlan(Integer existingPrimaryVlan, Integer existingSecondaryVlan, Integer requestedVlan) { + return requestedVlan.equals(existingPrimaryVlan) || requestedVlan.equals(existingSecondaryVlan); } protected Network.PVlanType getNetworkPvlanType(long networkId, List existingPvlan) { @@ -770,6 +777,38 @@ public class NetworkDaoImpl extends GenericDaoBaseimplements Ne return existingPvlanType; } + @Override + public List listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri) { + final URI searchUri = BroadcastDomainType.fromString(broadcastUri); + if (!searchUri.getScheme().equalsIgnoreCase("vlan")) { + throw new CloudRuntimeException("VLAN requested but URI is not in the expected format: " + searchUri.toString()); + } + final String searchRange = BroadcastDomainType.getValue(searchUri); + final List searchVlans = UriUtils.expandVlanUri(searchRange); + final List overlappingNetworks = new ArrayList<>(); + + final SearchCriteria sc = PhysicalNetworkSearch.create(); + sc.setParameters("physicalNetworkId", physicalNetworkId); + + for (final NetworkVO network : listBy(sc)) { + if (network.getBroadcastUri() == null || !network.getBroadcastUri().getScheme().equalsIgnoreCase("pvlan")) { + continue; + } + // Ensure existing and proposed VLAN don't overlap + final String networkVlanRange = BroadcastDomainType.getValue(network.getBroadcastUri()); + if (networkVlanRange == null || networkVlanRange.isEmpty()) { + continue; + } + List existingPvlan = UriUtils.expandPvlanUri(networkVlanRange); + if (isNetworkOverlappingRequestedPvlan(existingPvlan.get(0), existingPvlan.get(1), searchVlans.get(0))) { + overlappingNetworks.add(network); + break; + } + } + + return overlappingNetworks; + } + @Override public List listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType) { final URI searchUri = BroadcastDomainType.fromString(broadcastUri); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 79958ef8ea4..5bcc679eca1 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -798,14 +798,14 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv throw new ConfigurationException("Unable to find the router_proxy.sh"); } - _ovsPvlanDhcpHostPath = Script.findScript(networkScriptsDir, "ovs-pvlan-dhcp-host.sh"); + _ovsPvlanDhcpHostPath = Script.findScript(networkScriptsDir, "ovs-pvlan-kvm-dhcp-host.sh"); if (_ovsPvlanDhcpHostPath == null) { - throw new ConfigurationException("Unable to find the ovs-pvlan-dhcp-host.sh"); + throw new ConfigurationException("Unable to find the ovs-pvlan-kvm-dhcp-host.sh"); } - _ovsPvlanVmPath = Script.findScript(networkScriptsDir, "ovs-pvlan-vm.sh"); + _ovsPvlanVmPath = Script.findScript(networkScriptsDir, "ovs-pvlan-kvm-vm.sh"); if (_ovsPvlanVmPath == null) { - throw new ConfigurationException("Unable to find the ovs-pvlan-vm.sh"); + throw new ConfigurationException("Unable to find the ovs-pvlan-kvm-vm.sh"); } String value = (String)params.get("developer"); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPvlanSetupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPvlanSetupCommandWrapper.java index 3e01dc42599..23722a535e4 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPvlanSetupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPvlanSetupCommandWrapper.java @@ -19,22 +19,17 @@ package com.cloud.hypervisor.kvm.resource.wrapper; -import java.util.List; - import org.apache.log4j.Logger; import org.joda.time.Duration; -import org.libvirt.Connect; -import org.libvirt.LibvirtException; import com.cloud.agent.api.Answer; import com.cloud.agent.api.PvlanSetupCommand; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; -import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; import com.cloud.utils.script.Script; -@ResourceWrapper(handles = PvlanSetupCommand.class) +@ResourceWrapper(handles = PvlanSetupCommand.class) public final class LibvirtPvlanSetupCommandWrapper extends CommandWrapper { private static final Logger s_logger = Logger.getLogger(LibvirtPvlanSetupCommandWrapper.class); @@ -43,66 +38,50 @@ public final class LibvirtPvlanSetupCommandWrapper extends CommandWrapper ifaces = libvirtComputingResource.getInterfaces(conn, dhcpName); - final InterfaceDef guestNic = ifaces.get(0); - script.add(opr, "-b", guestBridgeName, "-p", primaryPvlan, "-i", isolatedPvlan, "-n", dhcpName, "-d", dhcpIp, "-m", dhcpMac, "-I", - guestNic.getDevName()); - } else { - script.add(opr, "-b", guestBridgeName, "-p", primaryPvlan, "-i", isolatedPvlan, "-n", dhcpName, "-d", dhcpIp, "-m", dhcpMac); - } + script.add(opr, pvlanType, "-b", guestBridgeName, "-p", primaryPvlan, "-s", isolatedPvlan, "-m", dhcpMac, + "-d", dhcpIp); + result = script.execute(); - result = script.execute(); - - if (result != null) { - s_logger.warn("Failed to program pvlan for dhcp server with mac " + dhcpMac); - return new Answer(command, false, result); - } else { - s_logger.info("Programmed pvlan for dhcp server with mac " + dhcpMac); - } - } else if (command.getType() == PvlanSetupCommand.Type.VM) { - final String ovsPvlanVmPath = libvirtComputingResource.getOvsPvlanVmPath(); - - final Script script = new Script(ovsPvlanVmPath, timeout, s_logger); - script.add(opr, "-b", guestBridgeName, "-p", primaryPvlan, "-i", isolatedPvlan, "-v", vmMac); - result = script.execute(); - - if (result != null) { - s_logger.warn("Failed to program pvlan for vm with mac " + vmMac); - return new Answer(command, false, result); - } else { - s_logger.info("Programmed pvlan for vm with mac " + vmMac); - } + if (result != null) { + s_logger.warn("Failed to program pvlan for dhcp server with mac " + dhcpMac); + } else { + s_logger.info("Programmed pvlan for dhcp server with mac " + dhcpMac); } - } catch (final LibvirtException e) { - s_logger.error("Error whislt executing OVS Setup command! ==> " + e.getMessage()); - return new Answer(command, false, e.getMessage()); } + + // We run this even for DHCP servers since they're all vms after all + final String ovsPvlanVmPath = libvirtComputingResource.getOvsPvlanVmPath(); + final Script script = new Script(ovsPvlanVmPath, timeout, s_logger); + script.add(opr, pvlanType, "-b", guestBridgeName, "-p", primaryPvlan, "-s", isolatedPvlan, "-m", vmMac); + result = script.execute(); + + if (result != null) { + s_logger.warn("Failed to program pvlan for vm with mac " + vmMac); + return new Answer(command, false, result); + } else { + s_logger.info("Programmed pvlan for vm with mac " + vmMac); + } + return new Answer(command, true, result); } } \ No newline at end of file diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index aa5f6ea8dd0..bd651f4c02c 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -4337,7 +4337,7 @@ public class LibvirtComputingResourceTest { @Test public void testPvlanSetupCommandDhcpAdd() { final String op = "add"; - final URI uri = URI.create("http://localhost"); + final URI uri = URI.create("pvlan://200-p200"); final String networkTag = "/105"; final String dhcpName = "dhcp"; final String dhcpMac = "00:00:00:00"; @@ -4345,46 +4345,24 @@ public class LibvirtComputingResourceTest { final PvlanSetupCommand command = PvlanSetupCommand.createDhcpSetup(op, uri, networkTag, dhcpName, dhcpMac, dhcpIp); - final LibvirtUtilitiesHelper libvirtUtilitiesHelper = Mockito.mock(LibvirtUtilitiesHelper.class); - final Connect conn = Mockito.mock(Connect.class); - final String guestBridgeName = "br0"; when(libvirtComputingResource.getGuestBridgeName()).thenReturn(guestBridgeName); - when(libvirtComputingResource.getTimeout()).thenReturn(Duration.ZERO); + final String ovsPvlanDhcpHostPath = "/pvlan"; when(libvirtComputingResource.getOvsPvlanDhcpHostPath()).thenReturn(ovsPvlanDhcpHostPath); - when(libvirtComputingResource.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper); - - final List ifaces = new ArrayList(); - final InterfaceDef nic = Mockito.mock(InterfaceDef.class); - ifaces.add(nic); - - try { - when(libvirtUtilitiesHelper.getConnectionByVmName(dhcpName)).thenReturn(conn); - when(libvirtComputingResource.getInterfaces(conn, dhcpName)).thenReturn(ifaces); - } catch (final LibvirtException e) { - fail(e.getMessage()); - } final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance(); assertNotNull(wrapper); final Answer answer = wrapper.execute(command, libvirtComputingResource); assertFalse(answer.getResult()); - - verify(libvirtComputingResource, times(1)).getLibvirtUtilitiesHelper(); - try { - verify(libvirtUtilitiesHelper, times(1)).getConnectionByVmName(dhcpName); - } catch (final LibvirtException e) { - fail(e.getMessage()); - } } @Test public void testPvlanSetupCommandVm() { final String op = "add"; - final URI uri = URI.create("http://localhost"); + final URI uri = URI.create("pvlan://200-p200"); final String networkTag = "/105"; final String vmMac = "00:00:00:00"; @@ -4404,52 +4382,10 @@ public class LibvirtComputingResourceTest { assertFalse(answer.getResult()); } - @SuppressWarnings("unchecked") - @Test - public void testPvlanSetupCommandDhcpException() { - final String op = "add"; - final URI uri = URI.create("http://localhost"); - final String networkTag = "/105"; - final String dhcpName = "dhcp"; - final String dhcpMac = "00:00:00:00"; - final String dhcpIp = "127.0.0.1"; - - final PvlanSetupCommand command = PvlanSetupCommand.createDhcpSetup(op, uri, networkTag, dhcpName, dhcpMac, dhcpIp); - - final LibvirtUtilitiesHelper libvirtUtilitiesHelper = Mockito.mock(LibvirtUtilitiesHelper.class); - - final String guestBridgeName = "br0"; - when(libvirtComputingResource.getGuestBridgeName()).thenReturn(guestBridgeName); - - when(libvirtComputingResource.getTimeout()).thenReturn(Duration.ZERO); - final String ovsPvlanDhcpHostPath = "/pvlan"; - when(libvirtComputingResource.getOvsPvlanDhcpHostPath()).thenReturn(ovsPvlanDhcpHostPath); - when(libvirtComputingResource.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper); - - try { - when(libvirtUtilitiesHelper.getConnectionByVmName(dhcpName)).thenThrow(LibvirtException.class); - } catch (final LibvirtException e) { - fail(e.getMessage()); - } - - final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance(); - assertNotNull(wrapper); - - final Answer answer = wrapper.execute(command, libvirtComputingResource); - assertFalse(answer.getResult()); - - verify(libvirtComputingResource, times(1)).getLibvirtUtilitiesHelper(); - try { - verify(libvirtUtilitiesHelper, times(1)).getConnectionByVmName(dhcpName); - } catch (final LibvirtException e) { - fail(e.getMessage()); - } - } - @Test public void testPvlanSetupCommandDhcpDelete() { final String op = "delete"; - final URI uri = URI.create("http://localhost"); + final URI uri = URI.create("pvlan://200-p200"); final String networkTag = "/105"; final String dhcpName = "dhcp"; final String dhcpMac = "00:00:00:00"; @@ -4457,15 +4393,12 @@ public class LibvirtComputingResourceTest { final PvlanSetupCommand command = PvlanSetupCommand.createDhcpSetup(op, uri, networkTag, dhcpName, dhcpMac, dhcpIp); - final LibvirtUtilitiesHelper libvirtUtilitiesHelper = Mockito.mock(LibvirtUtilitiesHelper.class); - final String guestBridgeName = "br0"; when(libvirtComputingResource.getGuestBridgeName()).thenReturn(guestBridgeName); - when(libvirtComputingResource.getTimeout()).thenReturn(Duration.ZERO); + final String ovsPvlanDhcpHostPath = "/pvlan"; when(libvirtComputingResource.getOvsPvlanDhcpHostPath()).thenReturn(ovsPvlanDhcpHostPath); - when(libvirtComputingResource.getLibvirtUtilitiesHelper()).thenReturn(libvirtUtilitiesHelper); final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance(); assertNotNull(wrapper); diff --git a/scripts/vm/network/ovs-pvlan-kvm-dhcp-host.sh b/scripts/vm/network/ovs-pvlan-kvm-dhcp-host.sh new file mode 100755 index 00000000000..46eceb96026 --- /dev/null +++ b/scripts/vm/network/ovs-pvlan-kvm-dhcp-host.sh @@ -0,0 +1,137 @@ +#!/bin/bash +# 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. + +#!/bin/bash + +# We're trying to do the impossible here by allowing pvlan on kvm / xen. As only God can do the impossible, and we've got too much ego to +# admit that we can't, we're just hacking our way around it. +# We're pretty much crafting two vlan headers, one with the primary vlan and the other with the secondary and with a few fancy rules +# it managed to work. But take note that the'res no checking over here for secondary vlan overlap. That has to be handled while +# creating the pvlan!! + +exec 2>&1 + +usage() { + printf "Usage: %s: (-A|-D) (-P/I/C) -b -p -s -m -d -h \n" $(basename $0) >&2 + exit 2 +} + +br= +pri_vlan= +sec_vlan= +vm_mac= +dhcp_ip= +op= +type= + +while getopts 'ADPICb:p:s:m:d:h' OPTION +do + case $OPTION in + A) op="add" + ;; + D) op="del" + ;; + P) type="P" + ;; + I) type="I" + ;; + C) type="C" + ;; + b) br="$OPTARG" + ;; + p) pri_vlan="$OPTARG" + ;; + s) sec_vlan="$OPTARG" + ;; + m) vm_mac="$OPTARG" + ;; + d) dhcp_ip="$OPTARG" + ;; + h) usage + exit 1 + ;; + esac +done + +if [ -z "$op" ] +then + echo Missing operation pararmeter! + exit 1 +fi + +if [ -z "$type" ] +then + echo Missing pvlan type pararmeter! + exit 1 +fi + +if [ -z "$br" ] +then + echo Missing parameter bridge! + exit 1 +fi + +if [ -z "$vm_mac" ] +then + echo Missing parameter VM MAC! + exit 1 +fi + +if [ -z "$pri_vlan" ] +then + echo Missing parameter primary vlan! + exit 1 +fi + +if [ -z "$sec_vlan" ] +then + echo Missing parameter secondary vlan! + exit 1 +fi + +if [ -z "$dhcp_ip" ] +then + echo Missing parameter DHCP IP! + exit 1 +fi + +find_port() { + mac=`echo "$1" | sed -e 's/:/\\\:/g'` + port=`ovs-vsctl --column ofport find interface external_ids:attached-mac="$mac" | tr -d ' ' | cut -d ':' -f 2` + echo $port +} + +ovs-vsctl set bridge $br protocols=OpenFlow10,OpenFlow11,OpenFlow12,OpenFlow13 +ovs-vsctl set Open_vSwitch . other_config:vlan-limit=2 + +if [ "$op" == "add" ] +then + dhcp_port=$(find_port $vm_mac) + + ovs-ofctl add-flow $br table=0,priority=200,arp,dl_vlan=$pri_vlan,nw_dst=$dhcp_ip,actions=strip_vlan,resubmit\(,1\) + ovs-ofctl add-flow $br table=1,priority=200,arp,dl_vlan=$sec_vlan,nw_dst=$dhcp_ip,actions=strip_vlan,output:$dhcp_port + + ovs-ofctl add-flow $br table=0,priority=100,udp,dl_vlan=$pri_vlan,nw_dst=255.255.255.255,tp_dst=67,actions=strip_vlan,resubmit\(,1\) + ovs-ofctl add-flow $br table=1,priority=100,udp,dl_vlan=$sec_vlan,nw_dst=255.255.255.255,tp_dst=67,actions=strip_vlan,output:$dhcp_port +else + ovs-ofctl del-flows --strict $br table=0,priority=200,arp,dl_vlan=$pri_vlan,nw_dst=$dhcp_ip + ovs-ofctl del-flows --strict $br table=1,priority=200,arp,dl_vlan=$sec_vlan,nw_dst=$dhcp_ip + + ovs-ofctl del-flows --strict $br table=0,priority=100,udp,dl_vlan=$pri_vlan,nw_dst=255.255.255.255,tp_dst=67 + ovs-ofctl del-flows --strict $br table=1,priority=100,udp,dl_vlan=$sec_vlan,nw_dst=255.255.255.255,tp_dst=67 +fi diff --git a/scripts/vm/network/ovs-pvlan-kvm-vm.sh b/scripts/vm/network/ovs-pvlan-kvm-vm.sh new file mode 100644 index 00000000000..7b4e2740f34 --- /dev/null +++ b/scripts/vm/network/ovs-pvlan-kvm-vm.sh @@ -0,0 +1,290 @@ +#!/bin/bash +# 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. + +#!/bin/bash + +# We're trying to do the impossible here by allowing pvlan on kvm / xen. As only God can do the impossible, and we've got too much ego to +# admit that we can't, we're just hacking our way around it. +# We're pretty much crafting two vlan headers, one with the primary vlan and the other with the secondary and with a few fancy rules +# it managed to work. But take note that the'res no checking over here for secondary vlan overlap. That has to be handled while +# creating the pvlan!! + +exec 2>&1 + +usage() { + printf "Usage: %s: (-A|-D) (-P/I/C) -b -p -s -m -h \n" $(basename $0) >&2 + exit 2 +} + +br= +pri_vlan= +sec_vlan= +vm_mac= +op= +type= + +while getopts 'ADPICb:p:s:m:h' OPTION +do + case $OPTION in + A) op="add" + ;; + D) op="del" + ;; + P) type="P" + ;; + I) type="I" + ;; + C) type="C" + ;; + b) br="$OPTARG" + ;; + p) pri_vlan="$OPTARG" + ;; + s) sec_vlan="$OPTARG" + ;; + m) vm_mac="$OPTARG" + ;; + h) usage + exit 1 + ;; + esac +done + +if [ -z "$op" ] +then + echo Missing operation pararmeter! + exit 1 +fi + +if [ -z "$type" ] +then + echo Missing pvlan type pararmeter! + exit 1 +fi + +if [ -z "$br" ] +then + echo Missing parameter bridge! + exit 1 +fi + +if [ -z "$vm_mac" ] +then + echo Missing parameter VM MAC! + exit 1 +fi + +if [ -z "$pri_vlan" ] +then + echo Missing parameter primary vlan! + exit 1 +fi + +if [ -z "$sec_vlan" ] +then + echo Missing parameter secondary vlan! + exit 1 +fi + +find_port() { + mac=`echo "$1" | sed -e 's/:/\\\:/g'` + port=`ovs-vsctl --column ofport find interface external_ids:attached-mac="$mac" | tr -d ' ' | cut -d ':' -f 2` + echo $port +} + +find_port_group() { + ovs-ofctl -O OpenFlow13 dump-groups $br | grep group_id=$1, | sed -e 's/.*type=all,//g' -e 's/bucket=actions=//g' -e 's/resubmit(,1)//g' -e 's/strip_vlan,//g' -e 's/pop_vlan,//g' -e 's/output://g' -e 's/^,//g' -e 's/,$//g' -e 's/,,/,/g' -e 's/ //g' +} + +# try to find the physical link to outside, only supports eth and em prefix now +trunk_port=`ovs-ofctl show $br | egrep "\((eth|em)[0-9]" | cut -d '(' -f 1|tr -d ' '` +vm_port=$(find_port $vm_mac) + +# craft the vlan headers. Adding 4096 as in hex, it must be of the form 0x1XXX +pri_vlan_header=$((4096 + $pri_vlan)) +sec_vlan_header=$((4096 + $sec_vlan)) + +# Get the groups for broadcast. Ensure we end the group id with ',' so that we wont accidentally match groupid 111 with 1110. +# We're using the header value for the pri vlan port group, as anything from a promiscuous device has to go to every device in the vlan. +# Since we're creating a separate group for just the promiscuous devices, adding 4096 so that it'll be unique. Hence we're restricted to 4096 vlans! +# Not a big deal because if you have vxlan, why do you even need pvlan!! +pri_vlan_ports=$(find_port_group $pri_vlan_header) +sec_vlan_ports=$(find_port_group $sec_vlan) + +add_to_ports() { + if [ -z "$1" ] + then + # To ensure that we don't get trailing commas + echo "$2" + else + # Dont add it if it already exists + echo "$1" | grep -w -q "$2" && echo "$1" && return + echo "$2,$1" + fi +} + +del_from_ports() { + # Delete when only, begining, middle and end of string + echo "$1" | sed -e "s/^$2$//g" -e "s/^$2,//g" -e "s/,$2$//g" -e "s/,$2,/,/g" +} + +mod_group() { + # Ensure that we don't delete the prom port group, because if we do, the rules that have it go away! + actions=`echo "$2" | sed -e 's/,/,bucket=actions=/g'` + if [ "$1" == "$pri_vlan" ] + then + actions=`echo "$2" | sed -e 's/,/,bucket=actions=strip_vlan,output:/g'` + if [ -z "$2" ] + then + ovs-ofctl -O OpenFlow13 mod-group --may-create $br group_id=$1,type=all,bucket=resubmit\(,1\) + else + ovs-ofctl -O OpenFlow13 mod-group --may-create $br group_id=$1,type=all,bucket=resubmit\(,1\),bucket=actions=strip_vlan,output:$actions + fi + return + fi + if [ -z "$2" ] + then + ovs-ofctl -O OpenFlow13 del-groups $br group_id=$1 + else + ovs-ofctl -O OpenFlow13 mod-group --may-create $br group_id=$1,type=all,bucket=actions=$actions + fi +} + +cleanup_flows() { + ovs-ofctl del-flows $br --strict table=0,priority=70,dl_vlan=$pri_vlan,dl_dst=ff:ff:ff:ff:ff:ff + ovs-ofctl del-flows $br --strict table=1,priority=70,dl_vlan=$pri_vlan,dl_dst=ff:ff:ff:ff:ff:ff + ovs-ofctl -O OpenFlow13 del-groups $br group_id=$pri_vlan +} + +# Allow the neccessary protocols and QinQ +ovs-vsctl set bridge $br protocols=OpenFlow10,OpenFlow11,OpenFlow12,OpenFlow13 +ovs-vsctl set Open_vSwitch . other_config:vlan-limit=2 + +# So that we're friendly to non pvlan devices +ovs-ofctl add-flow $br priority=0,actions=NORMAL + +if [ "$op" == "add" ] +then + # From our pri vlan + if [ "$type" == "P" ] + then + ovs-ofctl add-flow $br table=0,priority=70,dl_vlan=$pri_vlan,dl_dst=$vm_mac,actions=strip_vlan,strip_vlan,output:$vm_port + else + ovs-ofctl add-flow $br table=0,priority=70,dl_vlan=$pri_vlan,dl_dst=$vm_mac,actions=strip_vlan,resubmit\(,1\) + fi + + # Accept from promiscuous + ovs-ofctl add-flow $br table=1,priority=70,dl_vlan=$pri_vlan,dl_dst=$vm_mac,actions=strip_vlan,output:$vm_port + # From others in our own community + if [ "$type" == "C" ] + then + ovs-ofctl add-flow $br table=1,priority=70,dl_vlan=$sec_vlan,dl_dst=$vm_mac,actions=strip_vlan,output:$vm_port + fi + # Allow only dhcp to isolated vm + if [ "$type" == "I" ] + then + ovs-ofctl add-flow $br table=1,priority=70,udp,dl_vlan=$sec_vlan,dl_dst=$vm_mac,tp_src=67,actions=strip_vlan,output:$vm_port + fi + + # Security101 + ovs-ofctl add-flow $br table=1,priority=0,actions=drop + + # If the dest isn't on our switch send it out + ovs-ofctl add-flow $br table=0,priority=60,dl_vlan=$pri_vlan,dl_src=$vm_mac,actions=output:$trunk_port + # QinQ the packet. Outter header is the primary vlan and inner is the secondary + ovs-ofctl add-flow -O OpenFlow13 $br table=0,priority=50,vlan_tci=0x0000,dl_src=$vm_mac,actions=push_vlan:0x8100,set_field:$sec_vlan_header-\>vlan_vid,push_vlan:0x8100,set_field:$pri_vlan_header-\>vlan_vid,resubmit:$trunk_port + + # BROADCASTS + # Create the respective groups + # pri_vlan_ports are the list of ports of all iso & comm dev for a give pvlan + if [ "$type" != "P" ] + then + pri_vlan_ports=$(add_to_ports "$pri_vlan_ports" "$vm_port") + mod_group $pri_vlan_header $pri_vlan_ports + fi + # sec_vlan_ports are the list of ports for a given secondary pvlan + sec_vlan_ports=$(add_to_ports "$sec_vlan_ports" "$vm_port") + mod_group $sec_vlan $sec_vlan_ports + + # Ensure we have the promiscuous port group because if we don't, it'll fail to create the following rule + prom_ports=$(find_port_group $pri_vlan) + mod_group $pri_vlan $prom_ports + + # From a device on this switch. Pass it to the trunk port and process it ourselves for other devices on the switch. + ovs-ofctl add-flow $br table=0,priority=300,dl_vlan=$pri_vlan,dl_src=$vm_mac,dl_dst=ff:ff:ff:ff:ff:ff,actions=output:$trunk_port,strip_vlan,group:$pri_vlan + # Got a packet from the trunk port from out pri vlan, pass it to pri_vlan_group which sends the packet out to the promiscuous devices as well as passes it onto table 1 + ovs-ofctl add-flow $br table=0,priority=70,dl_vlan=$pri_vlan,dl_dst=ff:ff:ff:ff:ff:ff,actions=strip_vlan,group:$pri_vlan + # From a promiscuous device, so send it to all community and isolated devices on this switch. Passed to all promiscuous devices in the prior step ^^ + ovs-ofctl add-flow $br table=1,priority=70,dl_vlan=$pri_vlan,dl_dst=ff:ff:ff:ff:ff:ff,actions=strip_vlan,group:$pri_vlan_header + # Since it's from a community, gotta braodcast it to all community devices + if [ "$type" == "C" ] + then + ovs-ofctl add-flow $br table=1,priority=70,dl_vlan=$sec_vlan,dl_dst=ff:ff:ff:ff:ff:ff,actions=strip_vlan,group:$sec_vlan + fi + # Allow only dhcp form isolated router to isolated vm + if [ "$type" == "I" ] + then + ovs-ofctl add-flow $br table=1,priority=70,udp,dl_vlan=$sec_vlan,tp_src=67,dl_dst=ff:ff:ff:ff:ff:ff,actions=strip_vlan,group:$sec_vlan + fi + +else + # Delete whatever we've added that's vm specific + ovs-ofctl del-flows $br --strict table=0,priority=70,dl_vlan=$pri_vlan,dl_dst=$vm_mac + + # Need to ge the vmport from the rules as it's already been removed from the switch + vm_port=`ovs-ofctl dump-flows $br | grep "priority=70,dl_vlan=$pri_vlan,dl_dst=$vm_mac" | tr ':' '\n' | tail -n 1` + ovs-ofctl del-flows $br --strict table=1,priority=70,dl_vlan=$pri_vlan,dl_dst=$vm_mac + if [ "$type" == "C" ] + then + ovs-ofctl del-flows $br --strict table=1,priority=70,dl_vlan=$sec_vlan,dl_dst=$vm_mac + fi + if [ "$type" == "I" ] + then + ovs-ofctl del-flows $br --strict table=1,priority=70,udp,dl_vlan=$sec_vlan,dl_dst=$vm_mac,tp_src=67 + fi + + ovs-ofctl del-flows $br --strict table=0,priority=60,dl_vlan=$pri_vlan,dl_src=$vm_mac + ovs-ofctl del-flows $br --strict table=0,priority=50,vlan_tci=0x0000,dl_src=$vm_mac + # For some ovs versions + ovs-ofctl del-flows $br --strict table=0,priority=50,vlan_tci=0x0000/0x1fff,dl_src=$vm_mac + + # Remove the port from the groups + pri_vlan_ports=$(del_from_ports "$pri_vlan_ports" "$vm_port") + mod_group $pri_vlan_header $pri_vlan_ports + sec_vlan_ports=$(del_from_ports "$sec_vlan_ports" "$vm_port") + mod_group $sec_vlan $sec_vlan_ports + + # Remove vm specific rules + ovs-ofctl del-flows $br --strict table=0,priority=300,dl_vlan=$pri_vlan,dl_src=$vm_mac,dl_dst=ff:ff:ff:ff:ff:ff + + # If the vm is going to be migrated but not yet removed. Remove the rules if it's the only vm in the vlan + res1=`ovs-vsctl --column _uuid find port tag=$pri_vlan | wc -l` + res2=`find_port $vm_mac | wc -l` + if [ "$res1" -eq 1 ] && [ "$res2" -eq 1 ] + then + cleanup_flows + fi + + # If no more vms exist on this host, clear up all the rules + result=`ovs-vsctl find port tag=$pri_vlan` + if [ -z "$result" ] + then + cleanup_flows + fi + +fi diff --git a/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java b/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java index ca918691d09..ec7da94f92f 100644 --- a/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java +++ b/server/src/test/java/com/cloud/network/dao/NetworkDaoTest.java @@ -52,4 +52,14 @@ public class NetworkDaoTest extends TestCase { Assert.assertFalse(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingSecondaryVlan, Network.PVlanType.Community, existingPrimaryVlan, requestedVlan, Network.PVlanType.Community)); } + + public void testNetworkOverlappingVlanPvlanTrue() { + Assert.assertTrue(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingSecondaryVlan, existingPrimaryVlan)); + Assert.assertTrue(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingSecondaryVlan, existingSecondaryVlan)); + } + + public void testNetworkOverlappingVlanPvlanFalse() { + Assert.assertFalse(dao.isNetworkOverlappingRequestedPvlan(existingPrimaryVlan, existingSecondaryVlan, requestedVlan)); + } + } diff --git a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java index 1a7c1f70090..a20ac6f5b70 100644 --- a/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java +++ b/server/src/test/java/com/cloud/vpc/dao/MockNetworkDaoImpl.java @@ -250,4 +250,9 @@ public class MockNetworkDaoImpl extends GenericDaoBase implemen public List listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri, Network.PVlanType pVlanType) { return null; } + + @Override + public List listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri) { + return null; + } } diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py index b6d1bae6b16..99d96d7bdef 100644 --- a/test/integration/smoke/test_network.py +++ b/test/integration/smoke/test_network.py @@ -1571,8 +1571,22 @@ class TestPrivateVlansL2Networks(cloudstackTestCase): isDvSwitch = True break - supported = isVmware and isDvSwitch - cls.vmwareHypervisorDvSwitchesForGuestTrafficNotPresent = not supported + # Supported hypervisor = KVM using OVS + isKVM = cls.hypervisor.lower() in ["kvm"] + isOVSEnabled = False + hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ + if isKVM : + # Test only if all the hosts use OVS + grepCmd = 'grep "network.bridge.type=openvswitch" /etc/cloudstack/agent/agent.properties' + hosts = list_hosts(cls.apiclient, type='Routing', hypervisor='kvm') + if len(hosts) > 0 : + isOVSEnabled = True + for host in hosts : + isOVSEnabled = isOVSEnabled and len(SshClient(host.ipaddress, port=22, user=hostConfig["username"], + passwd=hostConfig["password"]).execute(grepCmd)) != 0 + + supported = isVmware and isDvSwitch or isKVM and isOVSEnabled + cls.unsupportedHardware = not supported cls._cleanup = [] @@ -1730,7 +1744,7 @@ class TestPrivateVlansL2Networks(cloudstackTestCase): return vm_ip, eth_device @attr(tags=["advanced", "advancedns", "smoke", "pvlan"], required_hardware="true") - @skipTestIf("vmwareHypervisorDvSwitchesForGuestTrafficNotPresent") + @skipTestIf("unsupportedHardware") def test_l2_network_pvlan_connectivity(self): try: vm_community1_one = self.deploy_vm_multiple_nics("vmcommunity1one", self.l2_pvlan_community1) @@ -1788,6 +1802,7 @@ class TestPrivateVlansL2Networks(cloudstackTestCase): # Isolated PVLAN checks same_isolated = self.is_vm_l2_isolated_from_dest(vm_isolated1, vm_isolated1_eth, vm_isolated2_ip) isolated_to_community_isolated = self.is_vm_l2_isolated_from_dest(vm_isolated1, vm_isolated1_eth, vm_community1_one_ip) + isolated_to_promiscuous_isolated = self.is_vm_l2_isolated_from_dest(vm_isolated1, vm_isolated1_eth, vm_promiscuous1_ip) self.assertTrue( same_isolated, @@ -1797,6 +1812,10 @@ class TestPrivateVlansL2Networks(cloudstackTestCase): isolated_to_community_isolated, "VMs on isolated PVLANs must be isolated on layer 2 to Vms on community PVLAN" ) + self.assertFalse( + isolated_to_promiscuous_isolated, + "VMs on isolated PVLANs must not be isolated on layer 2 to Vms on promiscuous PVLAN", + ) # Promiscuous PVLAN checks same_promiscuous = self.is_vm_l2_isolated_from_dest(vm_promiscuous1, vm_promiscuous1_eth, vm_promiscuous2_ip) diff --git a/utils/src/main/java/com/cloud/utils/UriUtils.java b/utils/src/main/java/com/cloud/utils/UriUtils.java index 5b0b06e9a00..d2022c169f9 100644 --- a/utils/src/main/java/com/cloud/utils/UriUtils.java +++ b/utils/src/main/java/com/cloud/utils/UriUtils.java @@ -630,7 +630,7 @@ public class UriUtils { if (Strings.isNullOrEmpty(pvlanRange)) { return expandedVlans; } - String[] parts = pvlanRange.split("-i"); + String[] parts = pvlanRange.split("-\\w"); expandedVlans.add(Integer.parseInt(parts[0])); expandedVlans.add(Integer.parseInt(parts[1])); return expandedVlans; diff --git a/utils/src/main/java/com/cloud/utils/net/NetUtils.java b/utils/src/main/java/com/cloud/utils/net/NetUtils.java index 0fb055f5531..c60eac1a8d6 100644 --- a/utils/src/main/java/com/cloud/utils/net/NetUtils.java +++ b/utils/src/main/java/com/cloud/utils/net/NetUtils.java @@ -1471,6 +1471,24 @@ public class NetUtils { return URI.create("pvlan://" + primaryVlan + "-i" + isolatedPvlan); } + public static URI generateUriForPvlan(final String primaryVlan, final String isolatedPvlan, final String isolatedPvlanType) { + // Defaulting to isolated for backward compatibility + if (isolatedPvlan.length() < 1) { + return generateUriForPvlan(primaryVlan, isolatedPvlan); + } + char type = isolatedPvlanType.charAt(0); + switch(type) { + case 'c': + case 'C': + return URI.create("pvlan://" + primaryVlan + "-c" + isolatedPvlan); + case 'p': + case 'P': + return URI.create("pvlan://" + primaryVlan + "-p" + primaryVlan); + default : + return generateUriForPvlan(primaryVlan, isolatedPvlan); + } + } + public static String getPrimaryPvlanFromUri(final URI uri) { final String[] vlans = uri.getHost().split("-"); if (vlans.length < 1) { @@ -1488,6 +1506,31 @@ public class NetUtils { if (vlan.startsWith("i")) { return vlan.replace("i", " ").trim(); } + if (vlan.startsWith("p")) { + return vlan.replace("p", " ").trim(); + } + if (vlan.startsWith("c")) { + return vlan.replace("c", " ").trim(); + } + } + return null; + } + + public static String getPvlanTypeFromUri(final URI uri) { + final String[] vlans = uri.getHost().split("-"); + if (vlans.length < 2) { + return null; + } + for (final String vlan : vlans) { + if (vlan.startsWith("i")) { + return "I"; + } + if (vlan.startsWith("p")) { + return "P"; + } + if (vlan.startsWith("c")) { + return "C"; + } } return null; } diff --git a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java index 0ac1032bab9..1eff484a278 100644 --- a/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java +++ b/utils/src/test/java/com/cloud/utils/net/NetUtilsTest.java @@ -249,10 +249,20 @@ public class NetUtilsTest { } @Test - public void testGenerateUriForPvlan() { + public void testGenerateUriForIsolatedPvlan() { assertEquals("pvlan://123-i456", NetUtils.generateUriForPvlan("123", "456").toString()); } + @Test + public void testGenerateUriForCommunityPvlan() { + assertEquals("pvlan://123-c456", NetUtils.generateUriForPvlan("123", "456", "Community").toString()); + } + + @Test + public void testGenerateUriForPromiscuousPvlan() { + assertEquals("pvlan://123-p123", NetUtils.generateUriForPvlan("123", "123", "promiscuous").toString()); + } + @Test public void testGetPrimaryPvlanFromUri() { assertEquals("123", NetUtils.getPrimaryPvlanFromUri(NetUtils.generateUriForPvlan("123", "456")));