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.network.bigswitch;
  
  import java.net.URISyntaxException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  
  import org.apache.commons.lang3.StringUtils;
  import org.apache.commons.net.util.SubnetUtils;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  import org.bouncycastle.util.IPAddress;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.BcfAnswer;
  import com.cloud.agent.api.BcfCommand;
  import com.cloud.agent.api.CacheBcfTopologyCommand;
  import com.cloud.agent.api.GetControllerDataAnswer;
  import com.cloud.agent.api.GetControllerDataCommand;
  import com.cloud.agent.api.SyncBcfTopologyCommand;
  import com.cloud.dc.VlanVO;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.hypervisor.Hypervisor.HypervisorType;
  import com.cloud.network.BigSwitchBcfDeviceVO;
  import com.cloud.network.Network;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.Networks.BroadcastDomainType;
  import com.cloud.network.Networks.TrafficType;
  import com.cloud.network.bigswitch.TopologyData.Port;
  import com.cloud.network.dao.BigSwitchBcfDao;
  import com.cloud.network.dao.FirewallRulesCidrsDao;
  import com.cloud.network.dao.FirewallRulesCidrsVO;
  import com.cloud.network.dao.FirewallRulesDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.IPAddressVO;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.rules.FirewallRule.Purpose;
  import com.cloud.network.rules.FirewallRuleVO;
  import com.cloud.network.vpc.NetworkACLItem;
  import com.cloud.network.vpc.NetworkACLItemCidrsDao;
  import com.cloud.network.vpc.NetworkACLItemCidrsVO;
  import com.cloud.network.vpc.NetworkACLItemDao;
  import com.cloud.network.vpc.NetworkACLItemVO;
  import com.cloud.network.vpc.Vpc;
  import com.cloud.network.vpc.dao.VpcDao;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.SearchCriteria;
  import com.cloud.utils.db.Transaction;
  import com.cloud.utils.db.TransactionCallback;
  import com.cloud.utils.db.TransactionStatus;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.dao.NicDao;
  import com.cloud.vm.dao.VMInstanceDao;
  
  public class BigSwitchBcfUtils {
      protected Logger logger = LogManager.getLogger(getClass());
  
      private final NetworkDao _networkDao;
      private final NicDao _nicDao;
      private final VMInstanceDao _vmDao;
      private final HostDao _hostDao;
      private final VpcDao _vpcDao;
      private final BigSwitchBcfDao _bigswitchBcfDao;
      private final AgentManager _agentMgr;
      private final VlanDao _vlanDao;
      private final IPAddressDao _ipAddressDao;
      private final FirewallRulesDao _fwRulesDao;
      private final FirewallRulesCidrsDao _fwCidrsDao;
      private final NetworkACLItemDao _aclItemDao;
      private final NetworkACLItemCidrsDao _aclItemCidrsDao;
      private final NetworkModel _networkModel;
  
      public BigSwitchBcfUtils(NetworkDao networkDao,
              NicDao nicDao, VMInstanceDao vmDao, HostDao hostDao,
              VpcDao vpcDao, BigSwitchBcfDao bigswitchBcfDao,
              AgentManager agentMgr, VlanDao vlanDao, IPAddressDao ipAddressDao,
              FirewallRulesDao fwRulesDao, FirewallRulesCidrsDao fwCidrsDao,
              NetworkACLItemDao aclItemDao, NetworkACLItemCidrsDao aclItemCidrsDao,
              NetworkModel networkModel){
          _networkDao = networkDao;
          _nicDao = nicDao;
          _vmDao = vmDao;
          _hostDao = hostDao;
          _vpcDao = vpcDao;
          _bigswitchBcfDao = bigswitchBcfDao;
          _agentMgr = agentMgr;
          _vlanDao = vlanDao;
          _ipAddressDao = ipAddressDao;
          _fwRulesDao = fwRulesDao;
          _fwCidrsDao = fwCidrsDao;
          _aclItemDao = aclItemDao;
          _aclItemCidrsDao = aclItemCidrsDao;
          _networkModel = networkModel;
      }
  
      public ControlClusterData getControlClusterData(long physicalNetworkId){
          ControlClusterData cluster = new ControlClusterData();
  
          // reusable command to query all devices
          GetControllerDataCommand cmd = new GetControllerDataCommand();
  
          // retrieve all registered BCF devices
          List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listByPhysicalNetwork(physicalNetworkId);
          for (BigSwitchBcfDeviceVO d: devices){
              HostVO bigswitchBcfHost = _hostDao.findById(d.getHostId());
              if (bigswitchBcfHost == null){
                  continue;
              }
  
              _hostDao.loadDetails(bigswitchBcfHost);
              GetControllerDataAnswer answer = (GetControllerDataAnswer) _agentMgr.easySend(bigswitchBcfHost.getId(), cmd);
              if (answer != null){
                  if (answer.isPrimary()) {
                      cluster.setPrimary(bigswitchBcfHost);
                  } else {
                      cluster.setSecondary(bigswitchBcfHost);
                  }
              }
          }
          return cluster;
      }
  
      public TopologyData getTopology(){
          long physicalNetworkId;
          List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listAll();
          if(!devices.isEmpty()){
              physicalNetworkId = devices.get(0).getPhysicalNetworkId();
              return getTopology(physicalNetworkId);
          } else {
              return null;
          }
      }
  
      public NetworkVO getPublicNetwork(long physicalNetworkId){
          List<NetworkVO> pubNets = _networkDao.listByPhysicalNetworkTrafficType(physicalNetworkId, TrafficType.Public);
          return pubNets.get(0);
      }
  
      public TopologyData getTopology(long physicalNetworkId){
          List<NetworkVO> networks;
          List<NicVO> nics;
  
          networks = _networkDao.listByPhysicalNetworkTrafficType(physicalNetworkId, TrafficType.Guest);
  
          TopologyData topo = new TopologyData();
  
          // networks:
          // - all user created networks (VPC or non-VPC)
          // - one external network
          // routers:
          // - per VPC (handle in network loop)
          // - per stand-alone network (handle in network loop)
          // - one external router
  
          // handle external network first, only if NAT service is enabled
          if(networks != null) {
              if(!(networks.isEmpty()) && isNatEnabled()){
                  // get public net info - needed to set up source nat gateway
                  NetworkVO pubNet = getPublicNetwork(physicalNetworkId);
  
                  // locate subnet info
                  SearchCriteria<VlanVO> sc = _vlanDao.createSearchCriteria();
                  sc.setParameters("network_id", pubNet.getId());
                  VlanVO vlanVO = _vlanDao.findOneBy(sc);
  
                  // add tenant external network external
                  TopologyData.Network network = topo.new Network();
                  network.setId("external");
                  network.setName("external");
                  network.setTenantId("external");
                  network.setTenantName("external");
  
                  String pubVlan = null;
                  try {
                      pubVlan = BroadcastDomainType.getValue(vlanVO.getVlanTag());
                      if(StringUtils.isNumeric(pubVlan)){
                          network.setVlan(Integer.valueOf(pubVlan));
                      } else {
                          // untagged
                          pubVlan = "0";
                      }
                  } catch (URISyntaxException e) {
                      e.printStackTrace();
                  }
  
                  topo.addNetwork(network);
              }
          }
  
          // routerMap used internally for multiple updates to same tenant's router
          // add back to topo.routers after loop
          HashMap<String, RouterData> routerMap = new HashMap<String, RouterData>();
  
          for (NetworkVO netVO: networks){
              TopologyData.Network network = topo.new Network();
              network.setId(netVO.getUuid());
              network.setName(netVO.getName());
  
              Integer vlan = null;
              if (netVO.getBroadcastUri() != null) {
                  String vlanStr = BroadcastDomainType.getValue(netVO.getBroadcastUri());
                  if(StringUtils.isNumeric(vlanStr)){
                      vlan = Integer.valueOf(vlanStr);
                  } else {
                      // untagged
                      vlan = 0;
                  }
              }
              network.setVlan(vlan);
              network.setState(netVO.getState().name());
  
              nics = _nicDao.listByNetworkId(netVO.getId());
              List<Port> ports = new ArrayList<Port>();
              String tenantId = null;
              String tenantName = null;
  
              // if VPC network, assign BCF tenant id with vpc uuid
              Vpc vpc = null;
              if(netVO.getVpcId()!=null){
                  vpc = _vpcDao.acquireInLockTable(netVO.getVpcId());
              }
  
              if (vpc != null) {
                  tenantId = vpc.getUuid();
                  tenantName = vpc.getName();
              } else {
                  tenantId = netVO.getUuid();
                  tenantName = netVO.getName();
              }
  
              for(NicVO nic: nics){
                  NetworkData netData = new NetworkData();
                  TopologyData.Port p = topo.new Port();
  
                  p.setAttachmentInfo(netData.new AttachmentInfo(nic.getUuid(),nic.getMacAddress()));
  
                  VMInstanceVO vm = _vmDao.findById(nic.getInstanceId());
                  HostVO host = _hostDao.findById(vm.getHostId());
  
                  // if host not found, ignore this nic
                  if (host == null) {
                      continue;
                  }
  
                  String hostname = host.getName();
                  long zoneId = netVO.getDataCenterId();
                  String vmwareVswitchLabel = _networkModel.getDefaultGuestTrafficLabel(zoneId, HypervisorType.VMware);
                  String[] labelArray = null;
                  String vswitchName = null;
                  if(vmwareVswitchLabel!=null){
                      labelArray=vmwareVswitchLabel.split(",");
                      vswitchName = labelArray[0];
                  }
  
                  // hypervisor type:
                  //   kvm: ivs port name
                  //   vmware: specific portgroup naming convention
                  String pgName = "";
                  if (host.getHypervisorType() == HypervisorType.KVM){
                      pgName = hostname;
                  } else if (host.getHypervisorType() == HypervisorType.VMware){
                      pgName = hostname + "-" + vswitchName;
                  }
  
                  p.setHostId(pgName);
  
                  p.setSegmentInfo(netData.new SegmentInfo(BroadcastDomainType.Vlan.name(), vlan));
  
                  p.setOwner(BigSwitchBcfApi.getCloudstackInstanceId());
  
                  List<AttachmentData.Attachment.IpAddress> ipList = new ArrayList<AttachmentData.Attachment.IpAddress>();
                  ipList.add(new AttachmentData().getAttachment().new IpAddress(nic.getIPv4Address()));
                  p.setIpAddresses(ipList);
  
                  p.setId(nic.getUuid());
  
                  p.setMac(nic.getMacAddress());
  
                  netData.getNetwork().setId(network.getId());
                  netData.getNetwork().setName(network.getName());
                  netData.getNetwork().setTenantId(tenantId);
                  netData.getNetwork().setTenantName(tenantName);
                  netData.getNetwork().setState(netVO.getState().name());
  
                  p.setNetwork(netData.getNetwork());
                  ports.add(p);
              }
              network.setTenantId(tenantId);
              network.setTenantName(tenantName);
              network.setPorts(ports);
              topo.addNetwork(network);
  
              // add router for network
              RouterData routerData;
              if(tenantId != null){
                  if(!routerMap.containsKey(tenantId)){
                      routerData = new RouterData(tenantId);
                      routerMap.put(tenantId, routerData);
                  } else {
                      routerData = routerMap.get(tenantId);
                  }
  
                  routerData.getRouter().getAcls().addAll(listACLbyNetwork(netVO));
                  if(vpc != null){
                      routerData.getRouter().addExternalGateway(getPublicIpByVpc(vpc));
                  } else {
                      routerData.getRouter().addExternalGateway(getPublicIpByNetwork(netVO));
                  }
  
                  RouterInterfaceData intf = new RouterInterfaceData(tenantId, netVO.getGateway(), netVO.getCidr(),
                          netVO.getUuid(), netVO.getName());
                  routerData.getRouter().addInterface(intf);
              }
          }
  
          for(RouterData rd: routerMap.values()) {
              topo.addRouter(rd.getRouter());
          }
  
          return topo;
      }
  
      public String getPublicIpByNetwork(Network network){
          List<IPAddressVO> allocatedIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), true);
          for (IPAddressVO ip: allocatedIps){
              if(ip.isSourceNat()){
                  return ip.getAddress().addr();
              }
          }
          return null;
      }
  
      public String getPublicIpByVpc(Vpc vpc){
          List<IPAddressVO> allocatedIps = _ipAddressDao.listByAssociatedVpc(vpc.getId(), true);
          for (IPAddressVO ip: allocatedIps){
              if(ip.isSourceNat()){
                  return ip.getAddress().addr();
              }
          }
          return null;
      }
  
      public List<AclData> listACLbyNetwork(Network network){
          List<AclData> aclList = new ArrayList<AclData>();
  
          List<FirewallRuleVO> fwRules;
          fwRules = _fwRulesDao.listByNetworkAndPurposeAndNotRevoked(network.getId(), Purpose.Firewall);
          List<FirewallRulesCidrsVO> fwCidrList = null;
          SubnetUtils utils;
  
          for(FirewallRuleVO rule: fwRules){
              AclData acl = new AclData();
              acl.setId(rule.getUuid());
              acl.setPriority((int)rule.getId()); // CloudStack Firewall interface does not have priority
              acl.setIpProto(rule.getProtocol());
              String cidr = null;
              Integer port = rule.getSourcePortStart();
              fwCidrList = _fwCidrsDao.listByFirewallRuleId(rule.getId());
              if(fwCidrList != null){
                  if(fwCidrList.size()>1 || !rule.getSourcePortEnd().equals(port)){
                      continue;
                  } else {
                      cidr = fwCidrList.get(0).getCidr();
                  }
              }
              if (cidr == null || cidr.equalsIgnoreCase("0.0.0.0/0")) {
                  cidr = "";
              } else {
                  utils = new SubnetUtils(cidr);
                  if(!utils.getInfo().getNetworkAddress().equals(utils.getInfo().getAddress())){
                      continue;
                  }
              }
              acl.setSource(acl.new AclNetwork(cidr, port));
              acl.setAction("permit");
  
              aclList.add(acl);
          }
  
          List<NetworkACLItemVO> aclItems;
          List<NetworkACLItemCidrsVO> aclCidrList;
  
          if (network.getNetworkACLId() != null){
              aclItems = _aclItemDao.listByACL(network.getNetworkACLId());
              for(NetworkACLItem item: aclItems){
                  AclData acl = new AclData();
                  acl.setId(item.getUuid());
                  acl.setPriority(item.getNumber());
                  acl.setIpProto(item.getProtocol());
                  String cidr = null; // currently BCF supports single cidr policy
                  Integer port = item.getSourcePortStart(); // currently BCF supports single port policy
                  aclCidrList = _aclItemCidrsDao.listByNetworkACLItemId(item.getId());
                  if(aclCidrList != null){
                      if(aclCidrList.size()>1 || !item.getSourcePortEnd().equals(port)){
                          continue;
                      } else {
                          cidr = aclCidrList.get(0).getCidr();
                      }
                  }
                  if (cidr == null || cidr.equalsIgnoreCase("0.0.0.0/0")) {
                      cidr = "";
                  } else {
                      utils = new SubnetUtils(cidr);
                      if(!utils.getInfo().getNetworkAddress().equals(utils.getInfo().getAddress())){
                          continue;
                      }
                  }
                  acl.setSource(acl.new AclNetwork(cidr, port));
                  acl.setAction(item.getAction().name());
  
                  aclList.add(acl);
              }
          }
  
          return aclList;
      }
  
      public String syncTopologyToBcfHost(HostVO bigswitchBcfHost){
          SyncBcfTopologyCommand syncCmd;
          if(isNatEnabled()){
              syncCmd = new SyncBcfTopologyCommand(true, true);
          } else {
              syncCmd = new SyncBcfTopologyCommand(true, false);
          }
          BcfAnswer syncAnswer = (BcfAnswer) _agentMgr.easySend(bigswitchBcfHost.getId(), syncCmd);
          if (syncAnswer == null || !syncAnswer.getResult()) {
              logger.error("SyncBcfTopologyCommand failed");
              return null;
          }
          return syncAnswer.getHash();
      }
  
      public String syncTopologyToBcfHost(HostVO bigswitchBcfHost, boolean natEnabled){
          SyncBcfTopologyCommand syncCmd;
          if(natEnabled){
              syncCmd = new SyncBcfTopologyCommand(true, true);
          } else {
              syncCmd = new SyncBcfTopologyCommand(true, false);
          }
          BcfAnswer syncAnswer = (BcfAnswer) _agentMgr.easySend(bigswitchBcfHost.getId(), syncCmd);
          if (syncAnswer == null || !syncAnswer.getResult()) {
              logger.error("SyncBcfTopologyCommand failed");
              return null;
          }
          return syncAnswer.getHash();
      }
  
      public BcfAnswer sendBcfCommandWithNetworkSyncCheck(BcfCommand cmd, Network network)throws IllegalArgumentException{
          // get registered Big Switch controller
          ControlClusterData cluster = getControlClusterData(network.getPhysicalNetworkId());
          if(cluster.getPrimary()==null){
              return new BcfAnswer(cmd, new CloudRuntimeException("Big Switch Network controller temporarily unavailable"));
          }
  
          TopologyData topo = getTopology(network.getPhysicalNetworkId());
  
          cmd.setTopology(topo);
          BcfAnswer answer =  (BcfAnswer) _agentMgr.easySend(cluster.getPrimary().getId(), cmd);
  
          if (answer == null || !answer.getResult()) {
              logger.error ("BCF API Command failed");
              throw new IllegalArgumentException("Failed API call to Big Switch Network plugin");
          }
  
          String newHash = answer.getHash();
          if (cmd.isTopologySyncRequested()) {
              newHash = syncTopologyToBcfHost(cluster.getPrimary());
          }
          if(newHash != null){
              commitTopologyHash(network.getPhysicalNetworkId(), newHash);
          }
  
          HostVO secondary = cluster.getSecondary();
          if(secondary != null){
              TopologyData newTopo = getTopology(network.getPhysicalNetworkId());
              CacheBcfTopologyCommand cacheCmd = new CacheBcfTopologyCommand(newTopo);
              _agentMgr.easySend(cluster.getSecondary().getId(), cacheCmd);
          }
  
          return answer;
      }
  
      @DB
      public Boolean commitTopologyHash(long physicalNetworkId, final String hash) {
          final List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listByPhysicalNetwork(physicalNetworkId);
  
          return Transaction.execute(new TransactionCallback<Boolean>() {
              @Override
              public Boolean doInTransaction(TransactionStatus status) {
                  for (BigSwitchBcfDeviceVO d: devices){
                      d.setHash(hash);
                      _bigswitchBcfDao.update(d.getId(), d);
                  }
                  return true;
              }
          });
      }
  
      public Boolean isNatEnabled(){
          List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listAll();
          if(devices != null && !devices.isEmpty()){
              return devices.get(0).getNat();
          } else {
              return false;
          }
      }
  
      public Integer getSubnetMaskLength(String maskString){
          if(!IPAddress.isValidIPv4(maskString)){
              return null;
          }
          String[] octets = maskString.split("\\.");
          Integer bits = 0;
          for (String o: octets){
              switch(o){
              case "255":
                  bits += 8;
                  continue;
              case "254":
                  bits += 7;
                  return bits;
              case "252":
                  bits += 6;
                  return bits;
              case "248":
                  bits += 5;
                  return bits;
              case "240":
                  bits += 4;
                  return bits;
              case "224":
                  bits += 3;
                  return bits;
              case "192":
                  bits += 2;
                  return bits;
              case "128":
                  bits += 1;
                  return bits;
              case "0":
                  return bits;
              default:
                  throw new NumberFormatException("non-contiguous subnet mask not supported");
              }
          }
          return bits;
      }
  }
  