content: /*
   * 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.Map;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import javax.naming.ConfigurationException;
  
  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.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
  import com.cloud.network.Networks;
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.net.NetUtils;
  import com.cloud.utils.script.OutputInterpreter;
  import com.cloud.utils.script.Script;
  
  public class IvsVifDriver extends VifDriverBase {
      private int _timeout;
  
      private final Object _vnetBridgeMonitor = new Object();
      private String _modifyVlanPath;
      private String _modifyVxlanPath;
      private String _ivsIfUpPath;
      private String _controlCidr = NetUtils.getLinkLocalCIDR();
  
      @Override
      public void configure(Map<String, Object> params) throws ConfigurationException {
          super.configure(params);
          String networkScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.NETWORK_SCRIPTS_DIR);
  
          String utilScriptsDir = "scripts/util/";
  
          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");
          }
          _ivsIfUpPath = Script.findScript(utilScriptsDir, "qemu-ivs-ifup");
  
          _controlCidr = getControlCidr(_controlCidr);
      }
  
      @Override
      public InterfaceDef plug(NicTO nic, String guestOsType, String nicAdapter, Map<String, String> extraConfig) throws InternalErrorException, LibvirtException {
          LibvirtVMDef.InterfaceDef intf = new LibvirtVMDef.InterfaceDef();
  
          String vNetId = null;
          String protocol = null;
          if (nic.getBroadcastType() == Networks.BroadcastDomainType.Vlan || nic.getBroadcastType() == Networks.BroadcastDomainType.Vxlan) {
              vNetId = Networks.BroadcastDomainType.getValue(nic.getBroadcastUri());
              protocol = Networks.BroadcastDomainType.getSchemeValue(nic.getBroadcastUri()).scheme();
          }
  
          String vlanId = null;
          String logicalSwitchUuid = null;
          if (nic.getBroadcastType() == Networks.BroadcastDomainType.Vlan) {
              vlanId = Networks.BroadcastDomainType.getValue(nic.getBroadcastUri());
          } else if (nic.getBroadcastType() == Networks.BroadcastDomainType.Lswitch) {
              logicalSwitchUuid = Networks.BroadcastDomainType.getValue(nic.getBroadcastUri());
          } else if (nic.getBroadcastType() == Networks.BroadcastDomainType.Pvlan) {
              // TODO consider moving some of this functionality from NetUtils to Networks....
              vlanId = NetUtils.getPrimaryPvlanFromUri(nic.getBroadcastUri());
          }
          String trafficLabel = nic.getName();
          Integer networkRateKBps = getNetworkRateKbps(nic);
          if (nic.getType() == Networks.TrafficType.Guest) {
              if ((nic.getBroadcastType() == Networks.BroadcastDomainType.Vlan || nic.getBroadcastType() == Networks.BroadcastDomainType.Pvlan) &&
                      !vlanId.equalsIgnoreCase("untagged")) {
                  if (trafficLabel != null && !trafficLabel.isEmpty()) {
                      logger.debug("creating a vlan dev and bridge for guest traffic per traffic label " + trafficLabel);
                      intf.defEthernet("ivsnet-" + nic.getUuid().substring(0, 5), nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), _ivsIfUpPath, networkRateKBps);
                  } else {
                      throw new InternalErrorException("no traffic label ");
                  }
              }
          } 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 ((nic.getBroadcastType() == Networks.BroadcastDomainType.Vlan) && (vNetId != null) && (protocol != null) && (!vNetId.equalsIgnoreCase("untagged")) ||
                      (nic.getBroadcastType() == Networks.BroadcastDomainType.Vxlan)) {
                  if (trafficLabel != null && !trafficLabel.isEmpty()) {
                      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() == true) {
              intf.setPxeDisable(true);
          }
          return intf;
      }
  
      @Override
      public void unplug(InterfaceDef iface, boolean deleteBr) {
      }
  
      @Override
      public void attach(LibvirtVMDef.InterfaceDef iface) {
          Script.runSimpleBashScript("/usr/sbin/ivs-ctl add-port " + iface.getDevName());
      }
  
      @Override
      public void detach(LibvirtVMDef.InterfaceDef iface) {
          Script.runSimpleBashScript("/usr/sbin/ivs-ctl del-port " + iface.getDevName());
      }
  
  
      private void createControlNetwork() throws LibvirtException {
          createControlNetwork(_bridges.get("linklocal"));
      }
  
      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 = "";
          brName = generateVnetBrName(nic, vNetId);
          createVnet(vNetId, nic, brName, protocol);
          return brName;
      }
  
      private String generateVnetBrName(String pifName, String vnetId) {
          return "br" + pifName + "-" + vnetId;
      }
  
      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, 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) {
          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()) {
                  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, logger);
              command.add("-o", "delete");
              command.add("-v", vNetId);
              command.add("-p", pName);
              command.add("-b", brName);
  
              final String result = command.execute();
              if (result != null) {
                  logger.debug("Delete bridge " + brName + " failed: " + result);
              }
          }
      }
  
      private void deleteExitingLinkLocalRouteTable(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[2].equalsIgnoreCase(linkLocalBr)) {
                      Script.runSimpleBashScript("ip route del " + _controlCidr);
                  } 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));
          }
      }
  
      @Override
      public void createControlNetwork(String privBrName) {
          deleteExitingLinkLocalRouteTable(privBrName);
          if (!isBridgeExists(privBrName)) {
              Script.runSimpleBashScript("ip link add " + privBrName + " type bridge; ip link set " + privBrName + " up");
              Script.runSimpleBashScript("ip address add " + NetUtils.getLinkLocalAddressFromCIDR(_controlCidr) + " dev " + privBrName, _timeout);
          }
      }
  
      @Override
      public void deleteBr(NicTO nic) {
      }
  
      private boolean isBridgeExists(String bridgeName) {
          File f = new File("/sys/devices/virtual/net/" + bridgeName);
          if (f.exists()) {
              return true;
          } else {
              return false;
          }
      }
  }
  