| /* |
| * 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.hypervisor.kvm.resource; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.naming.ConfigurationException; |
| |
| import com.cloud.utils.net.NetUtils; |
| import com.cloud.utils.script.OutputInterpreter; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.log4j.Logger; |
| import org.libvirt.LibvirtException; |
| |
| import com.cloud.agent.api.to.NicTO; |
| import com.cloud.agent.properties.AgentProperties; |
| import com.cloud.agent.properties.AgentPropertiesFileHandler; |
| import com.cloud.exception.InternalErrorException; |
| import com.cloud.network.Networks; |
| import com.cloud.utils.NumbersUtil; |
| import com.cloud.utils.script.Script; |
| |
| public class BridgeVifDriver extends VifDriverBase { |
| |
| private static final Logger s_logger = Logger.getLogger(BridgeVifDriver.class); |
| private int _timeout; |
| |
| private final Object _vnetBridgeMonitor = new Object(); |
| private String _modifyVlanPath; |
| private String _modifyVxlanPath; |
| private String _controlCidr = NetUtils.getLinkLocalCIDR(); |
| private Long libvirtVersion; |
| |
| @Override |
| public void configure(Map<String, Object> params) throws ConfigurationException { |
| |
| super.configure(params); |
| |
| getPifs(); |
| |
| // Set the domr scripts directory |
| params.put("domr.scripts.dir", "scripts/network/domr/kvm"); |
| |
| String networkScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.NETWORK_SCRIPTS_DIR); |
| |
| _controlCidr = getControlCidr(_controlCidr); |
| |
| String value = (String)params.get("scripts.timeout"); |
| _timeout = NumbersUtil.parseInt(value, 30 * 60) * 1000; |
| |
| _modifyVlanPath = Script.findScript(networkScriptsDir, "modifyvlan.sh"); |
| if (_modifyVlanPath == null) { |
| throw new ConfigurationException("Unable to find modifyvlan.sh"); |
| } |
| _modifyVxlanPath = Script.findScript(networkScriptsDir, "modifyvxlan.sh"); |
| if (_modifyVxlanPath == null) { |
| throw new ConfigurationException("Unable to find modifyvxlan.sh"); |
| } |
| |
| libvirtVersion = (Long) params.get("libvirtVersion"); |
| if (libvirtVersion == null) { |
| libvirtVersion = 0L; |
| } |
| } |
| |
| public void getPifs() { |
| final File dir = new File("/sys/devices/virtual/net"); |
| final File[] netdevs = dir.listFiles(); |
| final List<String> bridges = new ArrayList<String>(); |
| for (File netdev : netdevs) { |
| final File isbridge = new File(netdev.getAbsolutePath() + "/bridge"); |
| final String netdevName = netdev.getName(); |
| s_logger.debug("looking in file " + netdev.getAbsolutePath() + "/bridge"); |
| if (isbridge.exists()) { |
| s_logger.debug("Found bridge " + netdevName); |
| bridges.add(netdevName); |
| } |
| } |
| |
| String guestBridgeName = _libvirtComputingResource.getGuestBridgeName(); |
| String publicBridgeName = _libvirtComputingResource.getPublicBridgeName(); |
| |
| for (final String bridge : bridges) { |
| s_logger.debug("looking for pif for bridge " + bridge); |
| final String pif = getPif(bridge); |
| if (_libvirtComputingResource.isPublicBridge(bridge)) { |
| _pifs.put("public", pif); |
| } |
| if (guestBridgeName != null && bridge.equals(guestBridgeName)) { |
| _pifs.put("private", pif); |
| } |
| _pifs.put(bridge, pif); |
| } |
| |
| // guest(private) creates bridges on a pif, if private bridge not found try pif direct |
| // This addresses the unnecessary requirement of someone to create an unused bridge just for traffic label |
| if (_pifs.get("private") == null) { |
| s_logger.debug("guest(private) traffic label '" + guestBridgeName + "' not found as bridge, looking for physical interface"); |
| final File dev = new File("/sys/class/net/" + guestBridgeName); |
| if (dev.exists()) { |
| s_logger.debug("guest(private) traffic label '" + guestBridgeName + "' found as a physical device"); |
| _pifs.put("private", guestBridgeName); |
| } |
| } |
| |
| // public creates bridges on a pif, if private bridge not found try pif direct |
| // This addresses the unnecessary requirement of someone to create an unused bridge just for traffic label |
| if (_pifs.get("public") == null) { |
| s_logger.debug("public traffic label '" + publicBridgeName+ "' not found as bridge, looking for physical interface"); |
| final File dev = new File("/sys/class/net/" + publicBridgeName); |
| if (dev.exists()) { |
| s_logger.debug("public traffic label '" + publicBridgeName + "' found as a physical device"); |
| _pifs.put("public", publicBridgeName); |
| } |
| } |
| |
| s_logger.debug("done looking for pifs, no more bridges"); |
| } |
| |
| private String getPif(final String bridge) { |
| String pif = matchPifFileInDirectory(bridge); |
| final File vlanfile = new File("/proc/net/vlan/" + pif); |
| |
| if (vlanfile.isFile()) { |
| pif = Script.runSimpleBashScript("grep ^Device\\: /proc/net/vlan/" + pif + " | awk {'print $2'}"); |
| } |
| |
| return pif; |
| } |
| |
| private String matchPifFileInDirectory(final String bridgeName) { |
| final File brif = new File("/sys/devices/virtual/net/" + bridgeName + "/brif"); |
| |
| if (!brif.isDirectory()) { |
| final File pif = new File("/sys/class/net/" + bridgeName); |
| if (pif.isDirectory()) { |
| // if bridgeName already refers to a pif, return it as-is |
| return bridgeName; |
| } |
| s_logger.debug("failing to get physical interface from bridge " + bridgeName + ", does " + brif.getAbsolutePath() + "exist?"); |
| return ""; |
| } |
| |
| final File[] interfaces = brif.listFiles(); |
| |
| for (File anInterface : interfaces) { |
| final String fname = anInterface.getName(); |
| s_logger.debug("matchPifFileInDirectory: file name '" + fname + "'"); |
| if (LibvirtComputingResource.isInterface(fname)) { |
| return fname; |
| } |
| } |
| |
| s_logger.debug("failing to get physical interface from bridge " + bridgeName + ", did not find an eth*, bond*, team*, vlan*, em*, p*p*, ens*, eno*, enp*, or enx* in " + brif.getAbsolutePath()); |
| return ""; |
| } |
| |
| protected boolean isBroadcastTypeVlanOrVxlan(final NicTO nic) { |
| return nic != null && (nic.getBroadcastType() == Networks.BroadcastDomainType.Vlan |
| || nic.getBroadcastType() == Networks.BroadcastDomainType.Vxlan); |
| } |
| |
| protected boolean isValidProtocolAndVnetId(final String vNetId, final String protocol) { |
| return vNetId != null && protocol != null && !vNetId.equalsIgnoreCase("untagged"); |
| } |
| |
| @Override |
| public LibvirtVMDef.InterfaceDef plug(NicTO nic, String guestOsType, String nicAdapter, Map<String, String> extraConfig) throws InternalErrorException, LibvirtException { |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("nic=" + nic); |
| if (nicAdapter != null && !nicAdapter.isEmpty()) { |
| s_logger.debug("custom nic adapter=" + nicAdapter); |
| } |
| } |
| |
| LibvirtVMDef.InterfaceDef intf = new LibvirtVMDef.InterfaceDef(); |
| |
| String vNetId = null; |
| String protocol = null; |
| if (isBroadcastTypeVlanOrVxlan(nic)) { |
| vNetId = Networks.BroadcastDomainType.getValue(nic.getBroadcastUri()); |
| protocol = Networks.BroadcastDomainType.getSchemeValue(nic.getBroadcastUri()).scheme(); |
| } else if (nic.getBroadcastType() == Networks.BroadcastDomainType.Lswitch) { |
| throw new InternalErrorException("Nicira NVP Logicalswitches are not supported by the BridgeVifDriver"); |
| } |
| String trafficLabel = nic.getName(); |
| Integer networkRateKBps = 0; |
| if (libvirtVersion > ((10 * 1000 + 10))) { |
| networkRateKBps = (nic.getNetworkRateMbps() != null && nic.getNetworkRateMbps().intValue() != -1) ? nic.getNetworkRateMbps().intValue() * 128 : 0; |
| } |
| |
| if (nic.getType() == Networks.TrafficType.Guest) { |
| if (isBroadcastTypeVlanOrVxlan(nic) && isValidProtocolAndVnetId(vNetId, protocol)) { |
| if (trafficLabel != null && !trafficLabel.isEmpty()) { |
| s_logger.debug("creating a vNet dev and bridge for guest traffic per traffic label " + trafficLabel); |
| String brName = createVnetBr(vNetId, trafficLabel, protocol); |
| intf.defBridgeNet(brName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); |
| } else { |
| String brName = createVnetBr(vNetId, _bridges.get("private"), protocol); |
| intf.defBridgeNet(brName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); |
| } |
| } else { |
| String brname = ""; |
| if (trafficLabel != null && !trafficLabel.isEmpty()) { |
| brname = trafficLabel; |
| } else { |
| brname = _bridges.get("guest"); |
| } |
| intf.defBridgeNet(brname, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); |
| } |
| } else if (nic.getType() == Networks.TrafficType.Control) { |
| /* Make sure the network is still there */ |
| createControlNetwork(); |
| intf.defBridgeNet(_bridges.get("linklocal"), null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter)); |
| } else if (nic.getType() == Networks.TrafficType.Public) { |
| if (isBroadcastTypeVlanOrVxlan(nic) && isValidProtocolAndVnetId(vNetId, protocol)) { |
| if (trafficLabel != null && !trafficLabel.isEmpty()) { |
| s_logger.debug("creating a vNet dev and bridge for public traffic per traffic label " + trafficLabel); |
| String brName = createVnetBr(vNetId, trafficLabel, protocol); |
| intf.defBridgeNet(brName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); |
| } else { |
| String brName = createVnetBr(vNetId, "public", protocol); |
| intf.defBridgeNet(brName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); |
| } |
| } else { |
| intf.defBridgeNet(_bridges.get("public"), null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); |
| } |
| } else if (nic.getType() == Networks.TrafficType.Management) { |
| intf.defBridgeNet(_bridges.get("private"), null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter)); |
| } else if (nic.getType() == Networks.TrafficType.Storage) { |
| String storageBrName = nic.getName() == null ? _bridges.get("private") : nic.getName(); |
| intf.defBridgeNet(storageBrName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter)); |
| } |
| if (nic.getPxeDisable()) { |
| intf.setPxeDisable(true); |
| } |
| |
| return intf; |
| } |
| |
| @Override |
| public void unplug(LibvirtVMDef.InterfaceDef iface, boolean deleteBr) { |
| deleteVnetBr(iface.getBrName(), deleteBr); |
| } |
| |
| @Override |
| public void attach(LibvirtVMDef.InterfaceDef iface) { |
| Script.runSimpleBashScript("ip link set " + iface.getDevName() + " master " + iface.getBrName()); |
| } |
| |
| @Override |
| public void detach(LibvirtVMDef.InterfaceDef iface) { |
| Script.runSimpleBashScript("test -d /sys/class/net/" + iface.getBrName() + "/brif/" + iface.getDevName() + " && ip link set " + iface.getDevName() + " nomaster"); |
| } |
| |
| private String generateVnetBrName(String pifName, String vnetId) { |
| return "br" + pifName + "-" + vnetId; |
| } |
| |
| private String generateVxnetBrName(String pifName, String vnetId) { |
| return "brvx-" + vnetId; |
| } |
| |
| private String createVnetBr(String vNetId, String pifKey, String protocol) throws InternalErrorException { |
| String nic = _pifs.get(pifKey); |
| if (nic == null || protocol.equals(Networks.BroadcastDomainType.Vxlan.scheme())) { |
| // if not found in bridge map, maybe traffic label refers to pif already? |
| File pif = new File("/sys/class/net/" + pifKey); |
| if (pif.isDirectory()) { |
| nic = pifKey; |
| } |
| } |
| String brName = ""; |
| if (protocol.equals(Networks.BroadcastDomainType.Vxlan.scheme())) { |
| brName = generateVxnetBrName(nic, vNetId); |
| } else { |
| brName = generateVnetBrName(nic, vNetId); |
| } |
| createVnet(vNetId, nic, brName, protocol); |
| return brName; |
| } |
| |
| private void createVnet(String vnetId, String pif, String brName, String protocol) throws InternalErrorException { |
| synchronized (_vnetBridgeMonitor) { |
| String script = _modifyVlanPath; |
| if (protocol.equals(Networks.BroadcastDomainType.Vxlan.scheme())) { |
| script = _modifyVxlanPath; |
| } |
| final Script command = new Script(script, _timeout, s_logger); |
| command.add("-v", vnetId); |
| command.add("-p", pif); |
| command.add("-b", brName); |
| command.add("-o", "add"); |
| |
| final String result = command.execute(); |
| if (result != null) { |
| throw new InternalErrorException("Failed to create vnet " + vnetId + ": " + result); |
| } |
| } |
| } |
| |
| private void deleteVnetBr(String brName, boolean deleteBr) { |
| synchronized (_vnetBridgeMonitor) { |
| String cmdout = Script.runSimpleBashScript("ls /sys/class/net/" + brName); |
| if (cmdout == null) |
| // Bridge does not exist |
| return; |
| cmdout = Script.runSimpleBashScript("ls /sys/class/net/" + brName + "/brif | tr '\n' ' '"); |
| if (cmdout != null && cmdout.contains("vnet")) { |
| // Active VM remains on that bridge |
| return; |
| } |
| |
| Pattern oldStyleBrNameRegex = Pattern.compile("^cloudVirBr(\\d+)$"); |
| Pattern brNameRegex = Pattern.compile("^br(\\S+)-(\\d+)$"); |
| Matcher oldStyleBrNameMatcher = oldStyleBrNameRegex.matcher(brName); |
| Matcher brNameMatcher = brNameRegex.matcher(brName); |
| |
| String pName = null; |
| String vNetId = null; |
| if (oldStyleBrNameMatcher.find()) { |
| // Actually modifyvlan.sh doesn't require pif name when deleting its bridge so far. |
| pName = "undefined"; |
| vNetId = oldStyleBrNameMatcher.group(1); |
| } else if (brNameMatcher.find()) { |
| if (brNameMatcher.group(1) != null || !brNameMatcher.group(1).isEmpty()) { |
| pName = brNameMatcher.group(1); |
| } else { |
| pName = "undefined"; |
| } |
| vNetId = brNameMatcher.group(2); |
| } |
| |
| if (vNetId == null || vNetId.isEmpty()) { |
| s_logger.debug("unable to get a vNet ID from name " + brName); |
| return; |
| } |
| |
| String scriptPath = null; |
| if (cmdout != null && cmdout.contains("vxlan")) { |
| scriptPath = _modifyVxlanPath; |
| } else { |
| scriptPath = _modifyVlanPath; |
| } |
| |
| final Script command = new Script(scriptPath, _timeout, s_logger); |
| command.add("-o", "delete"); |
| command.add("-v", vNetId); |
| command.add("-p", pName); |
| command.add("-b", brName); |
| if (cmdout != null && !cmdout.contains("vxlan")) { |
| command.add("-d", String.valueOf(deleteBr)); |
| } |
| |
| final String result = command.execute(); |
| if (result != null) { |
| s_logger.debug("Delete bridge " + brName + " failed: " + result); |
| } |
| } |
| } |
| |
| private void deleteExistingLinkLocalRouteTable(String linkLocalBr) { |
| Script command = new Script("/bin/bash", _timeout); |
| command.add("-c"); |
| command.add("ip route | grep " + _controlCidr); |
| OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); |
| String result = command.execute(parser); |
| boolean foundLinkLocalBr = false; |
| if (result == null && parser.getLines() != null) { |
| String[] lines = parser.getLines().split("\\n"); |
| for (String line : lines) { |
| String[] tokens = line.split(" "); |
| if (tokens != null && tokens.length < 2) { |
| continue; |
| } |
| final String device = tokens[2]; |
| if (StringUtils.isNotEmpty(device) && !device.equalsIgnoreCase(linkLocalBr)) { |
| Script.runSimpleBashScript("ip route del " + _controlCidr + " dev " + tokens[2]); |
| } else { |
| foundLinkLocalBr = true; |
| } |
| } |
| } |
| |
| if (!foundLinkLocalBr) { |
| Script.runSimpleBashScript("ip address add " + NetUtils.getLinkLocalAddressFromCIDR(_controlCidr) + " dev " + linkLocalBr); |
| Script.runSimpleBashScript("ip route add " + _controlCidr + " dev " + linkLocalBr + " src " + NetUtils.getLinkLocalGateway(_controlCidr)); |
| } |
| } |
| |
| private void createControlNetwork() { |
| createControlNetwork(_bridges.get("linklocal")); |
| } |
| |
| @Override |
| public void createControlNetwork(String privBrName) { |
| deleteExistingLinkLocalRouteTable(privBrName); |
| if (!isExistingBridge(privBrName)) { |
| Script.runSimpleBashScript("ip link add name " + privBrName + " type bridge"); |
| Script.runSimpleBashScript("ip link set " + privBrName + " up"); |
| Script.runSimpleBashScript("ip address add " + NetUtils.getLinkLocalAddressFromCIDR(_controlCidr) + " dev " + privBrName); |
| } |
| } |
| |
| @Override |
| public boolean isExistingBridge(String bridgeName) { |
| File f = new File("/sys/devices/virtual/net/" + bridgeName + "/bridge"); |
| if (f.exists()) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| public void deleteBr(NicTO nic) { |
| String vlanId = Networks.BroadcastDomainType.getValue(nic.getBroadcastUri()); |
| String trafficLabel = nic.getName(); |
| String pifName = _pifs.get(trafficLabel); |
| if (pifName == null) { |
| // if not found in bridge map, maybe traffic label refers to pif already? |
| File pif = new File("/sys/class/net/" + trafficLabel); |
| if (pif.isDirectory()) { |
| pifName = trafficLabel; |
| } |
| } |
| if (vlanId != null && pifName != null) { |
| String brName = generateVnetBrName(pifName, vlanId); |
| deleteVnetBr(brName, true); |
| } |
| } |
| } |