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.rules;
  
  import java.net.URI;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  
  import org.apache.cloudstack.network.topology.NetworkTopologyVisitor;
  
  import com.cloud.agent.api.Command;
  import com.cloud.agent.api.NetworkUsageCommand;
  import com.cloud.agent.manager.Commands;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.InsufficientCapacityException;
  import com.cloud.exception.ResourceUnavailableException;
  import com.cloud.network.IpAddress;
  import com.cloud.network.Network;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.Networks.BroadcastDomainType;
  import com.cloud.network.Networks.IsolationType;
  import com.cloud.network.PublicIpAddress;
  import com.cloud.network.VpcVirtualNetworkApplianceService;
  import com.cloud.network.dao.FirewallRulesDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.IPAddressVO;
  import com.cloud.network.router.VirtualRouter;
  import com.cloud.network.vpc.VpcManager;
  import com.cloud.network.vpc.VpcVO;
  import com.cloud.network.vpc.dao.VpcDao;
  import com.cloud.user.UserStatisticsVO;
  import com.cloud.user.dao.UserStatisticsDao;
  import com.cloud.utils.Pair;
  import com.cloud.vm.Nic;
  import com.cloud.vm.NicProfile;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.VirtualMachineManager;
  import com.cloud.vm.dao.NicDao;
  
  import org.apache.cloudstack.network.topology.NetworkTopology;
  import org.apache.cloudstack.network.topology.NetworkTopologyContext;
  
  public class NicPlugInOutRules extends RuleApplier {
  
  
      private final List<? extends PublicIpAddress> _ipAddresses;
  
      private Commands _netUsageCommands;
  
      public NicPlugInOutRules(final Network network, final List<? extends PublicIpAddress> ipAddresses) {
          super(network);
          _ipAddresses = ipAddresses;
      }
  
      @Override
      public boolean accept(final NetworkTopologyVisitor visitor, final VirtualRouter router) throws ResourceUnavailableException {
          _router = router;
  
          Pair<Map<String, PublicIpAddress>, Map<String, PublicIpAddress>> nicsToChange = getNicsToChangeOnRouter(visitor);
  
          Map<String, PublicIpAddress> nicsToPlug = nicsToChange.first();
          Map<String, PublicIpAddress> nicsToUnplug = nicsToChange.second();
  
          NetworkModel networkModel = visitor.getVirtualNetworkApplianceFactory().getNetworkModel();
          VirtualMachineManager itMgr = visitor.getVirtualNetworkApplianceFactory().getItMgr();
          NicDao nicDao = visitor.getVirtualNetworkApplianceFactory().getNicDao();
          VpcVirtualNetworkApplianceService routerService = visitor.getVirtualNetworkApplianceFactory().getRouterService();
  
          // de-associate IPs before unplugging nics
          if (!nicsToUnplug.isEmpty()) {
              NetworkTopologyContext networkTopologyContext = visitor.getVirtualNetworkApplianceFactory().getNetworkTopologyContext();
              final DataCenterDao dcDao = visitor.getVirtualNetworkApplianceFactory().getDcDao();
              final DataCenterVO dcVO = dcDao.findById(router.getDataCenterId());
              final NetworkTopology networkTopology = networkTopologyContext.retrieveNetworkTopology(dcVO);
  
              final String typeString = "vpc ip association before unplugging nics";
              final boolean isPodLevelException = false;
              final boolean failWhenDisconnect = false;
              final Long podId = null;
              final VpcIpAssociationRules ipAssociationRules = new VpcIpAssociationRules(_network, _ipAddresses);
              final boolean result = networkTopology.applyRules(_network, router, typeString, isPodLevelException, podId, failWhenDisconnect,
                      new RuleApplierWrapper<RuleApplier>(ipAssociationRules));
              if (!result) {
                  logger.warn("Failed to de-associate IPs before unplugging nics");
                  return false;
              }
          }
  
          // 1) Unplug the nics
          for (Entry<String, PublicIpAddress> entry : nicsToUnplug.entrySet()) {
              PublicIpAddress ip = entry.getValue();
              NicVO nic = nicDao.findByIp4AddressAndNetworkIdAndInstanceId(ip.getNetworkId(), _router.getId(), ip.getAddress().addr());
              if (nic != null) {
                  logger.info("Collect network statistics for nic " + nic + " from router " + _router);
                  routerService.collectNetworkStatistics(_router, nic);
              }
              Network publicNtwk = null;
              try {
                  publicNtwk = networkModel.getNetwork(entry.getValue().getNetworkId());
                  URI broadcastUri = BroadcastDomainType.Vlan.toUri(entry.getKey());
                  itMgr.removeVmFromNetwork(_router, publicNtwk, broadcastUri);
              } catch (ConcurrentOperationException e) {
                  logger.warn("Failed to remove router " + _router + " from vlan " + entry.getKey() + " in public network " + publicNtwk + " due to ", e);
                  return false;
              }
          }
  
          _netUsageCommands = new Commands(Command.OnError.Continue);
          VpcDao vpcDao = visitor.getVirtualNetworkApplianceFactory().getVpcDao();
          VpcVO vpc = vpcDao.findById(_router.getVpcId());
  
          // 2) Plug the nics
          for (String vlanTag : nicsToPlug.keySet()) {
              PublicIpAddress ip = nicsToPlug.get(vlanTag);
              // have to plug the nic(s)
              NicProfile defaultNic = new NicProfile();
              if (ip.isSourceNat()) {
                  defaultNic.setDefaultNic(true);
              }
              defaultNic.setIPv4Address(ip.getAddress().addr());
              defaultNic.setIPv4Gateway(ip.getGateway());
              defaultNic.setIPv4Netmask(ip.getNetmask());
              defaultNic.setMacAddress(ip.getMacAddress());
              defaultNic.setBroadcastType(BroadcastDomainType.Vlan);
              defaultNic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(ip.getVlanTag()));
              defaultNic.setIsolationUri(IsolationType.Vlan.toUri(ip.getVlanTag()));
  
              NicProfile publicNic = null;
              Network publicNtwk = null;
              try {
                  publicNtwk = networkModel.getNetwork(ip.getNetworkId());
                  publicNic = itMgr.addVmToNetwork(_router, publicNtwk, defaultNic);
              } catch (ConcurrentOperationException e) {
                  logger.warn("Failed to add router " + _router + " to vlan " + vlanTag + " in public network " + publicNtwk + " due to ", e);
              } catch (InsufficientCapacityException e) {
                  logger.warn("Failed to add router " + _router + " to vlan " + vlanTag + " in public network " + publicNtwk + " due to ", e);
              } finally {
                  if (publicNic == null) {
                      logger.warn("Failed to add router " + _router + " to vlan " + vlanTag + " in public network " + publicNtwk);
                      return false;
                  }
              }
              // Create network usage commands. Send commands to router after
              // IPAssoc
              NetworkUsageCommand netUsageCmd = new NetworkUsageCommand(_router.getPrivateIpAddress(), _router.getInstanceName(), true, defaultNic.getIPv4Address(), vpc.getCidr());
              _netUsageCommands.addCommand(netUsageCmd);
  
              UserStatisticsDao userStatsDao = visitor.getVirtualNetworkApplianceFactory().getUserStatsDao();
              UserStatisticsVO stats = userStatsDao.findBy(_router.getAccountId(), _router.getDataCenterId(), publicNtwk.getId(), publicNic.getIPv4Address(), _router.getId(),
                      _router.getType().toString());
              if (stats == null) {
                  stats = new UserStatisticsVO(_router.getAccountId(), _router.getDataCenterId(), publicNic.getIPv4Address(), _router.getId(), _router.getType().toString(),
                          publicNtwk.getId());
                  userStatsDao.persist(stats);
              }
          }
  
          // The visit will be done from the AdvancedNetworkTopology, after the
          // VpcIpAssociation is done.
          return true;
      }
  
      public List<? extends PublicIpAddress> getIpAddresses() {
          return _ipAddresses;
      }
  
      public Commands getNetUsageCommands() {
          return _netUsageCommands;
      }
  
      private Pair<Map<String, PublicIpAddress>, Map<String, PublicIpAddress>> getNicsToChangeOnRouter(final NetworkTopologyVisitor visitor) {
          // 1) check which nics need to be plugged/unplugged and plug/unplug them
  
          final Map<String, PublicIpAddress> nicsToPlug = new HashMap<String, PublicIpAddress>();
          final Map<String, PublicIpAddress> nicsToUnplug = new HashMap<String, PublicIpAddress>();
  
          VpcManager vpcMgr = visitor.getVirtualNetworkApplianceFactory().getVpcMgr();
          NicDao nicDao = visitor.getVirtualNetworkApplianceFactory().getNicDao();
          IPAddressDao ipAddressDao = visitor.getVirtualNetworkApplianceFactory().getIpAddressDao();
          FirewallRulesDao rulesDao = visitor.getVirtualNetworkApplianceFactory().getFirewallRulesDao();
  
          // find out nics to unplug
          for (PublicIpAddress ip : _ipAddresses) {
              long publicNtwkId = ip.getNetworkId();
  
              // if ip is not associated to any network, and there are no firewall
              // rules, release it on the backend
              if (!vpcMgr.isIpAllocatedToVpc(ip)) {
                  ip.setState(IpAddress.State.Releasing);
              }
  
              if (ip.getState() == IpAddress.State.Releasing) {
                  NicVO nic = nicDao.findByIp4AddressAndNetworkIdAndInstanceId(publicNtwkId, _router.getId(), ip.getAddress().addr());
                  if (nic != null) {
                      final List<IPAddressVO> allIps = ipAddressDao.listByAssociatedVpc(ip.getVpcId(), null);
                      boolean ipUpdated = false;
                      for (IPAddressVO allIp : allIps) {
                          if (allIp.getId() != ip.getId() && allIp.getVlanId() == ip.getVlanId()
                                  && (allIp.isSourceNat()
                                  || rulesDao.countRulesByIpIdAndState(allIp.getId(), FirewallRule.State.Active) > 0
                                  || (allIp.isOneToOneNat() && allIp.getRuleState() == null))) {
                              logger.debug("Updating the nic " + nic + " with new ip address " + allIp.getAddress().addr());
                              nic.setIPv4Address(allIp.getAddress().addr());
                              nicDao.update(nic.getId(), nic);
                              ipUpdated = true;
                              break;
                          }
                      }
                      if (!ipUpdated) {
                          nicsToUnplug.put(ip.getVlanTag(), ip);
                          logger.debug("Need to unplug the nic for ip=" + ip + "; vlan=" + ip.getVlanTag() + " in public network id =" + publicNtwkId);
                      }
                  }
              }
          }
  
          // find out nics to plug
          for (PublicIpAddress ip : _ipAddresses) {
              URI broadcastUri = BroadcastDomainType.Vlan.toUri(ip.getVlanTag());
              long publicNtwkId = ip.getNetworkId();
  
              // if ip is not associated to any network, and there are no firewall
              // rules, release it on the backend
              if (!vpcMgr.isIpAllocatedToVpc(ip)) {
                  ip.setState(IpAddress.State.Releasing);
              }
  
              if (ip.getState() == IpAddress.State.Allocated || ip.getState() == IpAddress.State.Allocating) {
                  // nic has to be plugged only when there are no nics for this
                  // vlan tag exist on VR
                  Nic nic = nicDao.findByNetworkIdInstanceIdAndBroadcastUri(publicNtwkId, _router.getId(), broadcastUri.toString());
  
                  if (nic == null && nicsToPlug.get(ip.getVlanTag()) == null) {
                      nicsToPlug.put(ip.getVlanTag(), ip);
                      logger.debug("Need to plug the nic for ip=" + ip + "; vlan=" + ip.getVlanTag() + " in public network id =" + publicNtwkId);
                  } else {
                      final PublicIpAddress nicToUnplug = nicsToUnplug.get(ip.getVlanTag());
                      if (nicToUnplug != null) {
                          NicVO nicVO = nicDao.findByIp4AddressAndNetworkIdAndInstanceId(publicNtwkId, _router.getId(), nicToUnplug.getAddress().addr());
                          nicVO.setIPv4Address(ip.getAddress().addr());
                          nicDao.update(nicVO.getId(), nicVO);
                          logger.debug("Updated the nic " + nicVO + " with the new ip address " + ip.getAddress().addr());
                          nicsToUnplug.remove(ip.getVlanTag());
                      }
                  }
              }
          }
  
          Pair<Map<String, PublicIpAddress>, Map<String, PublicIpAddress>> nicsToChange = new Pair<Map<String, PublicIpAddress>, Map<String, PublicIpAddress>>(nicsToPlug,
                  nicsToUnplug);
  
          return nicsToChange;
      }
  }
  