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 org.apache.cloudstack.network.contrail.management;
  
  import java.io.IOException;
  import java.net.URI;
  import java.util.List;
  
  import javax.inject.Inject;
  
  import org.apache.cloudstack.network.contrail.model.InstanceIpModel;
  import org.apache.cloudstack.network.contrail.model.VMInterfaceModel;
  import org.apache.cloudstack.network.contrail.model.VirtualMachineModel;
  import org.apache.cloudstack.network.contrail.model.VirtualNetworkModel;
  
  import com.cloud.dc.DataCenter;
  import com.cloud.dc.DataCenter.NetworkType;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.deploy.DeployDestination;
  import com.cloud.deploy.DeploymentPlan;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.InsufficientAddressCapacityException;
  import com.cloud.exception.InsufficientVirtualNetworkCapacityException;
  import com.cloud.network.IpAddressManager;
  import com.cloud.network.Network;
  import com.cloud.network.Network.State;
  import com.cloud.network.NetworkProfile;
  import com.cloud.network.Networks.AddressFormat;
  import com.cloud.network.Networks.BroadcastDomainType;
  import com.cloud.network.Networks.Mode;
  import com.cloud.network.Networks.TrafficType;
  import com.cloud.network.PhysicalNetwork;
  import com.cloud.network.addr.PublicIp;
  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.dao.PhysicalNetworkDao;
  import com.cloud.network.dao.PhysicalNetworkVO;
  import com.cloud.network.guru.NetworkGuru;
  import com.cloud.offering.NetworkOffering;
  import com.cloud.user.Account;
  import com.cloud.user.AccountManager;
  import com.cloud.utils.component.AdapterBase;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.utils.net.NetUtils;
  import com.cloud.vm.Nic.ReservationStrategy;
  import com.cloud.vm.NicProfile;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.ReservationContext;
  import com.cloud.vm.VMInstanceVO;
  import com.cloud.vm.VirtualMachineProfile;
  import com.cloud.vm.dao.NicDao;
  
  import net.juniper.contrail.api.types.MacAddressesType;
  import net.juniper.contrail.api.types.VirtualMachineInterface;
  
  public class ContrailGuru extends AdapterBase implements NetworkGuru {
      @Inject
      NetworkDao _networkDao;
      @Inject
      ContrailManager _manager;
      @Inject
      NicDao _nicDao;
      @Inject
      IPAddressDao _ipAddressDao;
      @Inject
      AccountManager _accountMgr;
      @Inject
      IpAddressManager _ipAddrMgr;
      @Inject
      PhysicalNetworkDao _physicalNetworkDao;
      @Inject
      DataCenterDao _dcDao;
  
      private static final TrafficType[] TrafficTypes = {TrafficType.Guest};
  
      private boolean canHandle(NetworkOffering offering, NetworkType networkType, PhysicalNetwork physicalNetwork) {
          if (physicalNetwork == null) {
              // Physical network can be false for system network during initial setup of CloudStack
              return false;
          }
  
          if (_manager.getRouterOffering() == null || _manager.getVpcRouterOffering() == null) {
              // FIXME The resource is apparently not configured, we need another way to check this.
              return false;
          }
  
          if (networkType == NetworkType.Advanced
                  && (offering.getId() == _manager.getRouterOffering().getId() || offering.getId() == _manager.getVpcRouterOffering().getId())
                  && isMyTrafficType(offering.getTrafficType())
                  && offering.getGuestType() == Network.GuestType.Isolated
                  && physicalNetwork.getIsolationMethods().contains("L3VPN"))
              return true;
  
          return false;
      }
  
      @Override
      public String getName() {
          return "ContrailGuru";
      }
  
      @Override
      public Network design(NetworkOffering offering, DeploymentPlan plan, Network userSpecified, String name, Long vpcId, Account owner) {
          // Check of the isolation type of the related physical network is L3VPN
          PhysicalNetworkVO physnet = _physicalNetworkDao.findById(plan.getPhysicalNetworkId());
          DataCenter dc = _dcDao.findById(plan.getDataCenterId());
          if (!canHandle(offering, dc.getNetworkType(),physnet)) {
              logger.debug("Refusing to design this network");
              return null;
          }
          NetworkVO network =
                  new NetworkVO(offering.getTrafficType(), Mode.Dhcp, BroadcastDomainType.Lswitch, offering.getId(), State.Allocated, plan.getDataCenterId(),
                          plan.getPhysicalNetworkId(), offering.isRedundantRouter());
          if (userSpecified.getCidr() != null) {
              network.setCidr(userSpecified.getCidr());
              network.setGateway(userSpecified.getGateway());
          }
          logger.debug("Allocated network {}{}", userSpecified, network.getCidr() == null ? "" : " subnet: " + network.getCidr());
          return network;
      }
  
      @Override
      public void setup(Network network, long networkId) {
          // do nothing
      }
  
      @Override
      public Network implement(Network network, NetworkOffering offering, DeployDestination destination, ReservationContext context)
              throws InsufficientVirtualNetworkCapacityException {
          logger.debug("Implement network: {}, traffic type: {}", network, network.getTrafficType());
  
          VirtualNetworkModel vnModel = _manager.getDatabase().lookupVirtualNetwork(network.getUuid(), _manager.getCanonicalName(network), network.getTrafficType());
          if (vnModel == null) {
              vnModel = new VirtualNetworkModel(network, network.getUuid(), _manager.getCanonicalName(network), network.getTrafficType());
              vnModel.setProperties(_manager.getModelController(), network);
          }
  
          try {
              if (!vnModel.verify(_manager.getModelController())) {
                  vnModel.update(_manager.getModelController());
              }
          } catch (Exception ex) {
              logger.warn("virtual-network update: ", ex);
              return network;
          }
          _manager.getDatabase().getVirtualNetworks().add(vnModel);
  
          if (network.getVpcId() != null) {
              List<IPAddressVO> ips = _ipAddressDao.listByAssociatedVpc(network.getVpcId(), true);
              if (ips.isEmpty()) {
                  logger.debug("Creating a source nat ip for network " + network);
                  Account owner = _accountMgr.getAccount(network.getAccountId());
                  try {
                      PublicIp publicIp = _ipAddrMgr.assignSourceNatIpAddressToGuestNetwork(owner, network);
                      IPAddressVO ip = publicIp.ip();
                      ip.setVpcId(network.getVpcId());
                      _ipAddressDao.acquireInLockTable(ip.getId());
                      _ipAddressDao.update(ip.getId(), ip);
                      _ipAddressDao.releaseFromLockTable(ip.getId());
                  } catch (Exception e) {
                      logger.error("Unable to allocate source nat ip: " + e);
                  }
              }
          }
  
          return network;
      }
  
      /**
       * Allocate the NicProfile object.
       * At this point the UUID of the nic is not yet known. We defer allocating the VMI and instance-ip objects
       * until the reserve API is called because of this reason.
       */
      @Override
      public NicProfile allocate(Network network, NicProfile profile, VirtualMachineProfile vm) throws InsufficientVirtualNetworkCapacityException,
      InsufficientAddressCapacityException, ConcurrentOperationException {
          logger.debug(String.format("allocate NicProfile on %s", network));
  
          if (profile != null && profile.getRequestedIPv4() != null) {
              throw new CloudRuntimeException("Does not support custom ip allocation at this time: " + profile);
          }
          if (profile == null) {
              profile = new NicProfile(ReservationStrategy.Create, null, null, null, null);
          }
  
          profile.setReservationStrategy(ReservationStrategy.Start);
          URI broadcastUri = null;
          try {
              broadcastUri = new URI("vlan://untagged");
          } catch (Exception e) {
              logger.warn("unable to instantiate broadcast URI: " + e);
          }
          profile.setBroadcastUri(broadcastUri);
  
          return profile;
      }
  
      /**
       * Allocate the ip address (and mac) for the specified VM device.
       */
      @Override
      public void reserve(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context)
              throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException, ConcurrentOperationException {
          logger.debug("reserve NicProfile on network: " + network);
          logger.debug(String.format("nic: %s deviceId: %d", nic, nic.getDeviceId()));
  
          NicVO nicVO = _nicDao.findById(nic.getId());
          assert nicVO != null;
  
          VirtualNetworkModel vnModel = _manager.getDatabase().lookupVirtualNetwork(network.getUuid(), _manager.getCanonicalName(network), network.getTrafficType());
          /* Network must have been implemented */
          assert vnModel != null;
  
          VirtualMachineModel vmModel = _manager.getDatabase().lookupVirtualMachine(vm.getUuid());
          if (vmModel == null) {
              VMInstanceVO vmVo = (VMInstanceVO)vm.getVirtualMachine();
              vmModel = new VirtualMachineModel(vmVo, vm.getUuid());
              vmModel.setProperties(_manager.getModelController(), vmVo);
          }
  
          VMInterfaceModel vmiModel = vmModel.getVMInterface(nicVO.getUuid());
          if (vmiModel == null) {
              vmiModel = new VMInterfaceModel(nicVO.getUuid());
              vmiModel.addToVirtualMachine(vmModel);
              vmiModel.addToVirtualNetwork(vnModel);
          }
          try {
              vmiModel.build(_manager.getModelController(), (VMInstanceVO)vm.getVirtualMachine(), nicVO);
              vmiModel.setActive();
          } catch (IOException ex) {
              logger.error("virtual-machine-interface set", ex);
              return;
          }
  
          InstanceIpModel ipModel = vmiModel.getInstanceIp();
          if (ipModel == null) {
              ipModel = new InstanceIpModel(vm.getInstanceName(), nic.getDeviceId());
              ipModel.addToVMInterface(vmiModel);
          } else {
              logger.debug("Reuse existing instance-ip object on " + ipModel.getName());
          }
          if (nic.getIPv4Address() != null) {
              logger.debug("Nic using existing IP address " + nic.getIPv4Address());
              ipModel.setAddress(nic.getIPv4Address());
          }
  
          try {
              vmModel.update(_manager.getModelController());
          } catch (Exception ex) {
              logger.warn("virtual-machine update", ex);
              return;
          }
  
          _manager.getDatabase().getVirtualMachines().add(vmModel);
  
          VirtualMachineInterface vmi = vmiModel.getVMInterface();
          // allocate mac address
          if (nic.getMacAddress() == null) {
              MacAddressesType macs = vmi.getMacAddresses();
              if (macs == null) {
                  logger.debug(String.format("no mac address is allocated for Nic %s", nicVO));
              } else {
                  logger.info("VMI " + _manager.getVifNameByVmUuid(vm.getUuid(), nicVO.getDeviceId()) + " got mac address: " + macs.getMacAddress().get(0));
                  nic.setMacAddress(macs.getMacAddress().get(0));
              }
          }
  
          if (nic.getIPv4Address() == null) {
              logger.debug("Allocated IP address " + ipModel.getAddress());
              nic.setIPv4Address(ipModel.getAddress());
              if (network.getCidr() != null) {
                  nic.setIPv4Netmask(NetUtils.cidr2Netmask(network.getCidr()));
              }
              nic.setIPv4Gateway(network.getGateway());
              nic.setFormat(AddressFormat.Ip4);
          }
      }
  
      /**
       * When a VM is stopped this API is called to release transient resources.
       */
      @Override
      public boolean release(NicProfile nic, VirtualMachineProfile vm, String reservationId) {
  
          logger.debug(String.format("release NicProfile %s", nic));
  
          return true;
      }
  
      /**
       * Release permanent resources of a Nic (VMI and addresses).
       */
      @Override
      public void deallocate(Network network, NicProfile nic, VirtualMachineProfile vm) {
          logger.debug(String.format("deallocate NicProfile %s on %s", nic, network.getName()));
          NicVO nicVO = _nicDao.findById(nic.getId());
          assert nicVO != null;
  
          VirtualMachineModel vmModel = _manager.getDatabase().lookupVirtualMachine(vm.getUuid());
          if (vmModel == null) {
              return;
          }
          VMInterfaceModel vmiModel = vmModel.getVMInterface(nicVO.getUuid());
          if (vmiModel == null) {
              return;
          }
          try {
              vmiModel.destroy(_manager.getModelController());
          } catch (IOException ex) {
              return;
          }
          vmModel.removeSuccessor(vmiModel);
  
          if (!vmModel.hasDescendents()) {
              _manager.getDatabase().getVirtualMachines().remove(vmModel);
              try {
                  vmModel.delete(_manager.getModelController());
              } catch (IOException ex) {
                  logger.warn("virtual-machine delete", ex);
                  return;
              }
          }
  
      }
  
      @Override
      public void updateNicProfile(NicProfile profile, Network network) {
          // TODO Auto-generated method stub
          logger.debug(String.format("update NicProfile %s on %s", profile, network));
      }
  
      @Override
      public void shutdown(NetworkProfile network, NetworkOffering offering) {
          logger.debug("NetworkGuru shutdown");
          VirtualNetworkModel vnModel = _manager.getDatabase().lookupVirtualNetwork(network.getUuid(), _manager.getCanonicalName(network), network.getTrafficType());
          if (vnModel == null) {
              return;
          }
          try {
              _manager.getDatabase().getVirtualNetworks().remove(vnModel);
              vnModel.delete(_manager.getModelController());
          } catch (IOException e) {
              logger.warn("virtual-network delete", e);
          }
      }
  
      @Override
      public boolean trash(Network network, NetworkOffering offering) {
          // TODO Auto-generated method stub
          logger.debug("NetworkGuru trash");
          return true;
      }
  
      @Override
      public void updateNetworkProfile(NetworkProfile networkProfile) {
          // TODO Auto-generated method stub
          logger.debug("NetworkGuru updateNetworkProfile");
      }
  
      @Override
      public TrafficType[] getSupportedTrafficType() {
          return TrafficTypes;
      }
  
      @Override
      public boolean isMyTrafficType(TrafficType type) {
          for (TrafficType t : TrafficTypes) {
              if (t == type) {
                  return true;
              }
          }
          return false;
      }
  
  }
  