blob: 39ecc9182f00f0a6beaaf395a01a41a78bd92a42 [file] [log] [blame]
/*
* 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);
}
}
}