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.guru;
  
  import java.util.List;
  
  import javax.inject.Inject;
  
  import org.apache.cloudstack.api.ApiCommandResourceType;
  import org.apache.cloudstack.context.CallContext;
  import org.apache.commons.lang3.StringUtils;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.CreateBcfAttachmentCommand;
  import com.cloud.agent.api.CreateBcfRouterCommand;
  import com.cloud.agent.api.CreateBcfRouterInterfaceCommand;
  import com.cloud.agent.api.CreateBcfSegmentCommand;
  import com.cloud.agent.api.DeleteBcfSegmentCommand;
  import com.cloud.dc.DataCenter;
  import com.cloud.dc.DataCenter.NetworkType;
  import com.cloud.dc.VlanVO;
  import com.cloud.dc.dao.HostPodDao;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.deploy.DeployDestination;
  import com.cloud.deploy.DeploymentPlan;
  import com.cloud.event.ActionEventUtils;
  import com.cloud.event.EventTypes;
  import com.cloud.event.EventVO;
  import com.cloud.exception.InsufficientAddressCapacityException;
  import com.cloud.exception.InsufficientVirtualNetworkCapacityException;
  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.Network.GuestType;
  import com.cloud.network.Network.State;
  import com.cloud.network.NetworkMigrationResponder;
  import com.cloud.network.NetworkProfile;
  import com.cloud.network.NetworkService;
  import com.cloud.network.Networks.BroadcastDomainType;
  import com.cloud.network.PhysicalNetwork;
  import com.cloud.network.PhysicalNetwork.IsolationMethod;
  import com.cloud.network.bigswitch.BigSwitchBcfUtils;
  import com.cloud.network.dao.BigSwitchBcfDao;
  import com.cloud.network.dao.FirewallRulesCidrsDao;
  import com.cloud.network.dao.FirewallRulesDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.dao.PhysicalNetworkDao;
  import com.cloud.network.dao.PhysicalNetworkVO;
  import com.cloud.network.vpc.NetworkACLItemCidrsDao;
  import com.cloud.network.vpc.NetworkACLItemDao;
  import com.cloud.network.vpc.Vpc;
  import com.cloud.network.vpc.dao.VpcDao;
  import com.cloud.offering.NetworkOffering;
  import com.cloud.user.Account;
  import com.cloud.user.dao.AccountDao;
  import com.cloud.utils.db.SearchCriteria;
  import com.cloud.vm.NicProfile;
  import com.cloud.vm.ReservationContext;
  import com.cloud.vm.VirtualMachineProfile;
  import com.cloud.vm.dao.DomainRouterDao;
  import com.cloud.vm.dao.VMInstanceDao;
  
  /**
   * BigSwitchBcfGuestNetworkGuru is responsible for creating and removing virtual networks.
   * Current implementation leverages GuestNetworkGuru to create VLAN based virtual networks
   * and map it to a Big Switch Big Cloud Fabric (BCF) Segment.  It is called by the NetworkOrchestrator.
   *
   * When a VM is created and needs to be plugged into a BCF segment network, BigSwitchBcfElement is
   * called by NetworkOrchestrator to create a "port" and an "attachment" for each nic, and
   * register them with the controller to be plugged into the corresponding network. It also
   * removes them when the VM is destroyed.
   */
  public class BigSwitchBcfGuestNetworkGuru extends GuestNetworkGuru implements NetworkMigrationResponder {
  
      @Inject
      PhysicalNetworkDao _physicalNetworkDao;
      @Inject
      BigSwitchBcfDao _bigswitchBcfDao;
      @Inject
      HostDao _hostDao;
      @Inject
      AgentManager _agentMgr;
      @Inject
      DomainRouterDao _routerDao;
      @Inject
      VMInstanceDao _vmDao;
      @Inject
      AccountDao _accountDao;
      @Inject
      VpcDao _vpcDao;
      @Inject
      IPAddressDao _ipAddressDao;
      @Inject
      HostPodDao _podDao;
      @Inject
      NetworkService _netService;
      @Inject
      VlanDao _vlanDao;
      @Inject
      FirewallRulesDao _fwRulesDao;
      @Inject
      FirewallRulesCidrsDao _fwCidrsDao;
      @Inject
      NetworkACLItemDao _aclItemDao;
      @Inject
      NetworkACLItemCidrsDao _aclItemCidrsDao;
  
      private BigSwitchBcfUtils _bcfUtils = null;
  
      public BigSwitchBcfGuestNetworkGuru() {
          super();
          _isolationMethods = new IsolationMethod[] {new IsolationMethod("BCF_SEGMENT")};
      }
  
      @Override
      protected boolean canHandle(NetworkOffering offering, NetworkType networkType, PhysicalNetwork physicalNetwork) {
          if (networkType == NetworkType.Advanced && isMyTrafficType(offering.getTrafficType()) && offering.getGuestType() == Network.GuestType.Isolated &&
              isMyIsolationMethod(physicalNetwork)) {
              return true;
          } else {
              logger.trace("We only take care of Guest networks of type   " + GuestType.Isolated + " in zone of type " + NetworkType.Advanced);
              return false;
          }
      }
  
      @Override
      public Network design(NetworkOffering offering, DeploymentPlan plan, Network userSpecified, String name, Long vpcId, Account owner) {
          // Check if the isolation type of the physical network is BCF_SEGMENT, then delegate GuestNetworkGuru to design
          PhysicalNetworkVO physnet = _physicalNetworkDao.findById(plan.getPhysicalNetworkId());
          if (physnet == null || physnet.getIsolationMethods() == null || !physnet.getIsolationMethods().contains("BCF_SEGMENT")) {
              logger.debug("Refusing to design this network, the physical isolation type is not BCF_SEGMENT");
              return null;
          }
  
          List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listByPhysicalNetwork(physnet.getId());
          if (devices.isEmpty()) {
              logger.error(String.format("No BigSwitch Controller on physical network %s", physnet));
              return null;
          }
          for (BigSwitchBcfDeviceVO d: devices){
              logger.debug(String.format("BigSwitch Controller %s found on physical network %s", d, physnet));
          }
  
          logger.debug("Physical isolation type is BCF_SEGMENT, asking GuestNetworkGuru to design this network");
          NetworkVO networkObject = (NetworkVO)super.design(offering, plan, userSpecified, name, vpcId, owner);
          if (networkObject == null) {
              return null;
          }
          // Override the broadcast domain type
          networkObject.setBroadcastDomainType(BroadcastDomainType.Vlan);
  
          return networkObject;
      }
  
      @Override
      public Network implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context)
          throws InsufficientVirtualNetworkCapacityException {
          assert (network.getState() == State.Implementing) : "Why are we implementing " + network;
  
          bcfUtilsInit();
  
          long dcId = dest.getDataCenter().getId();
  
          long physicalNetworkId = _networkModel.findPhysicalNetworkId(dcId, offering.getTags(), offering.getTrafficType());
  
          NetworkVO implemented =
              new NetworkVO(network.getTrafficType(), network.getMode(), network.getBroadcastDomainType(), network.getNetworkOfferingId(), State.Allocated,
                  network.getDataCenterId(), physicalNetworkId, false);
  
          if (network.getGateway() != null) {
              implemented.setGateway(network.getGateway());
          }
  
          if (network.getCidr() != null) {
              implemented.setCidr(network.getCidr());
          }
  
          String vnetId = "";
          if (network.getBroadcastUri() == null) {
              vnetId = _dcDao.allocateVnet(dcId, physicalNetworkId, network.getAccountId(), context.getReservationId(), UseSystemGuestVlans.valueIn(network.getAccountId()));
              if (vnetId == null) {
                  throw new InsufficientVirtualNetworkCapacityException("Unable to allocate vnet as a " +
                          "part of network " + network + " implement ", DataCenter.class, dcId);
              }
              implemented.setBroadcastUri(BroadcastDomainType.Vlan.toUri(vnetId));
              ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), network.getAccountId(),
                      EventVO.LEVEL_INFO, EventTypes.EVENT_ZONE_VLAN_ASSIGN, "Assigned Zone Vlan: " + vnetId + " Network Id: " + network.getId(), network.getId(), ApiCommandResourceType.Network.toString(), 0);
          } else {
              implemented.setBroadcastUri(network.getBroadcastUri());
          }
  
          // Name is either the given name or the uuid
          String name = network.getName();
          if (name == null || name.isEmpty()) {
              name = ((NetworkVO)network).getUuid();
          }
          if (name.length() > 64) {
              name = name.substring(0, 63); // max length 64
          }
  
          // update fields in network object
          NetworkVO networkObject = (NetworkVO) network;
  
          // determine whether this is VPC network or stand-alone network
          Vpc vpc = null;
          if(network.getVpcId()!=null){
              vpc = _vpcDao.acquireInLockTable(network.getVpcId());
          }
  
          // use uuid of networkVO as network id in BSN
          String networkId = networkObject.getUuid();
  
          String tenantId;
          String tenantName;
          if (vpc != null) {
              tenantId = vpc.getUuid();
              tenantName = vpc.getName();
              _vpcDao.releaseFromLockTable(vpc.getId());
          } else {
              // use network in CS as tenant in BSN
              // for non-VPC networks, use network name and id as tenant name and id
              tenantId = networkId;
              tenantName = name;
          }
  
          // store tenantId in networkObject for NetworkOrchestrator implementNetwork use
          networkObject.setNetworkDomain(tenantId);
          // store tenant Id in implemented object for future actions (e.g., shutdown)
          implemented.setNetworkDomain(tenantId);
          String vlanStr = BroadcastDomainType.getValue(implemented.getBroadcastUri());
  
          // get public net info - needed to set up source nat gateway
          NetworkVO pubNet = _bcfUtils.getPublicNetwork(physicalNetworkId);
  
          // locate subnet info
          SearchCriteria<VlanVO> sc = _vlanDao.createSearchCriteria();
          sc.setParameters("network_id", pubNet.getId());
          VlanVO pubVlanVO = _vlanDao.findOneBy(sc);
          String pubVlanStr = pubVlanVO.getVlanTag();
  
          Integer vlan;
          if(StringUtils.isNumeric(vlanStr)){
              vlan = Integer.valueOf(vlanStr);
          } else {
              vlan = 0;
          }
          CreateBcfSegmentCommand cmd1 = new CreateBcfSegmentCommand(tenantId, tenantName, networkId, name, vlan);
  
          _bcfUtils.sendBcfCommandWithNetworkSyncCheck(cmd1, networkObject);
  
          if(_bcfUtils.isNatEnabled()){
              Integer pvlan;
              if(StringUtils.isNumeric(pubVlanStr)){
                  pvlan = Integer.valueOf(pubVlanStr);
              } else {
                  pvlan = 0;
              }
              CreateBcfSegmentCommand cmd2 = new CreateBcfSegmentCommand("external", "external", "external", "external", pvlan);
  
              _bcfUtils.sendBcfCommandWithNetworkSyncCheck(cmd2, networkObject);
  
              CreateBcfRouterCommand cmd3 = new CreateBcfRouterCommand(tenantId);
  
              _bcfUtils.sendBcfCommandWithNetworkSyncCheck(cmd3, network);
  
              CreateBcfRouterInterfaceCommand cmd5 = new CreateBcfRouterInterfaceCommand(tenantId, network.getUuid(), network.getCidr(),
                      network.getGateway(), network.getName());
  
              _bcfUtils.sendBcfCommandWithNetworkSyncCheck(cmd5, network);
          }
          return implemented;
      }
  
  
  
      @Override
      public void reserve(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context)
          throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException {
          // Have GuestNetworkGuru reserve for us
          super.reserve(nic, network, vm, dest, context);
      }
  
      @Override
      public boolean release(NicProfile nic, VirtualMachineProfile vm, String reservationId) {
          return super.release(nic, vm, reservationId);
      }
  
      @Override
      public void shutdown(NetworkProfile profile, NetworkOffering offering) {
          NetworkVO networkObject = _networkDao.findById(profile.getId());
          if (networkObject.getBroadcastDomainType() != BroadcastDomainType.Vlan || networkObject.getBroadcastUri() == null) {
              logger.warn(String.format("BroadcastUri is empty or incorrect for guest network %s", networkObject));
              return;
          }
  
          bcfUtilsInit();
  
          // tenantId stored in network domain field at creation
          String tenantId = networkObject.getNetworkDomain();
  
          String networkId = networkObject.getUuid();
  
          DeleteBcfSegmentCommand cmd = new DeleteBcfSegmentCommand(tenantId, networkId);
  
          _bcfUtils.sendBcfCommandWithNetworkSyncCheck(cmd, networkObject);
  
          super.shutdown(profile, offering);
      }
  
      @Override
      public boolean trash(Network network, NetworkOffering offering) {
          return super.trash(network, offering);
      }
  
      @Override
      public boolean prepareMigration(NicProfile nic, Network network,
              VirtualMachineProfile vm, DeployDestination dest,
              ReservationContext context) {
          bcfUtilsInit();
  
          // get arguments for CreateBcfAttachmentCommand
          // determine whether this is VPC network or stand-alone network
          Vpc vpc = null;
          if(network.getVpcId()!=null){
              vpc = _vpcDao.acquireInLockTable(network.getVpcId());
          }
  
          String networkId = network.getUuid();
  
          String tenantId;
          String tenantName;
          if (vpc != null) {
              tenantId = vpc.getUuid();
              tenantName = vpc.getName();
              boolean released = _vpcDao.releaseFromLockTable(vpc.getId());
              logger.debug(String.format("BCF guru release lock vpc: %s released? %s", vpc, released));
          } else {
              // use network id in CS as tenant in BSN
              // use network uuid as tenant id for non-VPC networks
              tenantId = networkId;
              tenantName = network.getName();
          }
  
          String hostname = dest.getHost().getName();
          long zoneId = network.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 (dest.getHost().getHypervisorType() == HypervisorType.KVM){
              pgName = hostname;
          } else if (dest.getHost().getHypervisorType() == HypervisorType.VMware){
              pgName = hostname + "-" + vswitchName;
          }
  
          String nicId = nic.getUuid();
          Integer vlan = Integer.valueOf(BroadcastDomainType.getValue(nic.getIsolationUri()));
          String ipv4 = nic.getIPv4Address();
          String mac = nic.getMacAddress();
  
          CreateBcfAttachmentCommand cmd = new CreateBcfAttachmentCommand(tenantId,
                  tenantName, networkId, pgName, nicId, vlan, ipv4, mac);
  
          _bcfUtils.sendBcfCommandWithNetworkSyncCheck(cmd, network);
  
          return true;
      }
  
      @Override
      public void rollbackMigration(NicProfile nic, Network network,
              VirtualMachineProfile vm, ReservationContext src,
              ReservationContext dst) {
          logger.debug("BCF guru rollback migration");
      }
  
      @Override
      public void commitMigration(NicProfile nic, Network network,
              VirtualMachineProfile vm, ReservationContext src,
              ReservationContext dst) {
          logger.debug("BCF guru commit migration");
      }
  
      private void bcfUtilsInit(){
          if (_bcfUtils == null) {
              _bcfUtils = new BigSwitchBcfUtils(_networkDao, _nicDao,
                      _vmDao, _hostDao, _vpcDao, _bigswitchBcfDao,
                      _agentMgr, _vlanDao, _ipAddressDao, _fwRulesDao,
                      _fwCidrsDao, _aclItemDao, _aclItemCidrsDao, _networkModel);
          }
      }
  }
  