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;
  
  import java.net.URI;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  import org.apache.cloudstack.api.ApiConstants;
  import org.apache.cloudstack.api.response.ExternalFirewallResponse;
  import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.network.ExternalNetworkDeviceManager.NetworkDevice;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.Answer;
  import com.cloud.agent.api.StartupCommand;
  import com.cloud.agent.api.StartupExternalFirewallCommand;
  import com.cloud.agent.api.routing.IpAssocCommand;
  import com.cloud.agent.api.routing.NetworkElementCommand;
  import com.cloud.agent.api.routing.RemoteAccessVpnCfgCommand;
  import com.cloud.agent.api.routing.SetFirewallRulesCommand;
  import com.cloud.agent.api.routing.SetPortForwardingRulesCommand;
  import com.cloud.agent.api.routing.SetStaticNatRulesCommand;
  import com.cloud.agent.api.routing.VpnUsersCfgCommand;
  import com.cloud.agent.api.to.FirewallRuleTO;
  import com.cloud.agent.api.to.IpAddressTO;
  import com.cloud.agent.api.to.PortForwardingRuleTO;
  import com.cloud.agent.api.to.StaticNatRuleTO;
  import com.cloud.configuration.Config;
  import com.cloud.dc.DataCenter;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.Vlan;
  import com.cloud.dc.VlanVO;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.exception.InsufficientCapacityException;
  import com.cloud.exception.InsufficientNetworkCapacityException;
  import com.cloud.exception.InvalidParameterValueException;
  import com.cloud.exception.ResourceUnavailableException;
  import com.cloud.host.DetailVO;
  import com.cloud.host.Host;
  import com.cloud.host.HostVO;
  import com.cloud.host.dao.HostDao;
  import com.cloud.host.dao.HostDetailsDao;
  import com.cloud.network.Networks.BroadcastDomainType;
  import com.cloud.network.Networks.TrafficType;
  import com.cloud.network.dao.ExternalFirewallDeviceDao;
  import com.cloud.network.dao.ExternalFirewallDeviceVO;
  import com.cloud.network.dao.FirewallRulesDao;
  import com.cloud.network.dao.IPAddressDao;
  import com.cloud.network.dao.IPAddressVO;
  import com.cloud.network.dao.InlineLoadBalancerNicMapDao;
  import com.cloud.network.dao.InlineLoadBalancerNicMapVO;
  import com.cloud.network.dao.LoadBalancerDao;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkExternalFirewallDao;
  import com.cloud.network.dao.NetworkExternalFirewallVO;
  import com.cloud.network.dao.NetworkServiceMapDao;
  import com.cloud.network.dao.NetworkVO;
  import com.cloud.network.dao.PhysicalNetworkDao;
  import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
  import com.cloud.network.dao.PhysicalNetworkServiceProviderVO;
  import com.cloud.network.dao.PhysicalNetworkVO;
  import com.cloud.network.dao.VpnUserDao;
  import com.cloud.network.rules.FirewallRule;
  import com.cloud.network.rules.FirewallRule.Purpose;
  import com.cloud.network.rules.FirewallRuleVO;
  import com.cloud.network.rules.PortForwardingRule;
  import com.cloud.network.rules.StaticNat;
  import com.cloud.network.rules.dao.PortForwardingRulesDao;
  import com.cloud.offering.NetworkOffering;
  import com.cloud.offerings.NetworkOfferingVO;
  import com.cloud.offerings.dao.NetworkOfferingDao;
  import com.cloud.resource.ResourceManager;
  import com.cloud.resource.ResourceState;
  import com.cloud.resource.ResourceStateAdapter;
  import com.cloud.resource.ServerResource;
  import com.cloud.resource.UnableDeleteHostException;
  import com.cloud.user.Account;
  import com.cloud.user.AccountManager;
  import com.cloud.user.dao.AccountDao;
  import com.cloud.user.dao.UserStatisticsDao;
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.Pair;
  import com.cloud.utils.component.AdapterBase;
  import com.cloud.utils.db.DB;
  import com.cloud.utils.db.GlobalLock;
  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.utils.net.NetUtils;
  import com.cloud.utils.net.UrlUtil;
  import com.cloud.vm.Nic.ReservationStrategy;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.dao.DomainRouterDao;
  import com.cloud.vm.dao.NicDao;
  
  public abstract class ExternalFirewallDeviceManagerImpl extends AdapterBase implements ExternalFirewallDeviceManager, ResourceStateAdapter {
  
      @Inject
      HostDao _hostDao;
      @Inject
      NetworkServiceMapDao _ntwkSrvcProviderDao;
      @Inject
      DataCenterDao _dcDao;
      @Inject
      NetworkModel _networkModel;
      @Inject
      NetworkOrchestrationService _networkMgr;
      @Inject
      InlineLoadBalancerNicMapDao _inlineLoadBalancerNicMapDao;
      @Inject
      NicDao _nicDao;
      @Inject
      AgentManager _agentMgr;
      @Inject
      ResourceManager _resourceMgr;
      @Inject
      IPAddressDao _ipAddressDao;
      @Inject
      VlanDao _vlanDao;
      @Inject
      NetworkOfferingDao _networkOfferingDao;
      @Inject
      AccountDao _accountDao;
      @Inject
      PhysicalNetworkDao _physicalNetworkDao;
      @Inject
      PhysicalNetworkServiceProviderDao _physicalNetworkServiceProviderDao;
      @Inject
      AccountManager _accountMgr;
      @Inject
      UserStatisticsDao _userStatsDao;
      @Inject
      NetworkDao _networkDao;
      @Inject
      DomainRouterDao _routerDao;
      @Inject
      LoadBalancerDao _loadBalancerDao;
      @Inject
      PortForwardingRulesDao _portForwardingRulesDao;
      @Inject
      ConfigurationDao _configDao;
      @Inject
      ExternalFirewallDeviceDao _externalFirewallDeviceDao;
      @Inject
      NetworkExternalFirewallDao _networkExternalFirewallDao;
      @Inject
      VpnUserDao _vpnUsersDao;
      @Inject
      HostDetailsDao _hostDetailDao;
      @Inject
      FirewallRulesDao _fwRulesDao;
  
      private long _defaultFwCapacity;
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          super.configure(name, params);
          _resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this);
          _defaultFwCapacity = NumbersUtil.parseLong(_configDao.getValue(Config.DefaultExternalFirewallCapacity.key()), 50);
          return true;
      }
  
      @Override
      @DB
      public ExternalFirewallDeviceVO addExternalFirewall(long physicalNetworkId, String url, String username, String password, final String deviceName,
          ServerResource resource) {
          String guid;
          PhysicalNetworkVO pNetwork = null;
          NetworkDevice ntwkDevice = NetworkDevice.getNetworkDevice(deviceName);
          long zoneId;
  
          if ((ntwkDevice == null) || (url == null) || (username == null) || (resource == null) || (password == null)) {
              throw new InvalidParameterValueException("Atleast one of the required parameters (url, username, password,"
                  + " server resource, zone id/physical network id) is not specified or a valid parameter.");
          }
  
          pNetwork = _physicalNetworkDao.findById(physicalNetworkId);
          if (pNetwork == null) {
              throw new InvalidParameterValueException("Could not find phyical network with ID: " + physicalNetworkId);
          }
          zoneId = pNetwork.getDataCenterId();
  
          final PhysicalNetworkServiceProviderVO ntwkSvcProvider =
              _physicalNetworkServiceProviderDao.findByServiceProvider(pNetwork.getId(), ntwkDevice.getNetworkServiceProvder());
          if (ntwkSvcProvider == null) {
              throw new CloudRuntimeException(String.format("Network Service Provider: %s is not enabled in the physical network: %s to add this device", ntwkDevice.getNetworkServiceProvder(), pNetwork));
          } else if (ntwkSvcProvider.getState() == PhysicalNetworkServiceProvider.State.Shutdown) {
              throw new CloudRuntimeException(String.format("Network Service Provider: %s is not added or in shutdown state in the physical network: %s to add this device", ntwkSvcProvider.getProviderName(), pNetwork));
          }
  
          URI uri;
          try {
              uri = new URI(url);
          } catch (Exception e) {
              logger.debug(e);
              throw new InvalidParameterValueException(e.getMessage());
          }
  
          String ipAddress = uri.getHost();
          Map hostDetails = new HashMap<String, String>();
          guid = getExternalNetworkResourceGuid(pNetwork.getId(), deviceName, ipAddress);
          hostDetails.put("name", guid);
          hostDetails.put("guid", guid);
          hostDetails.put("zoneId", String.valueOf(pNetwork.getDataCenterId()));
          hostDetails.put("ip", ipAddress);
          hostDetails.put("physicalNetworkId", String.valueOf(pNetwork.getId()));
          hostDetails.put("username", username);
          hostDetails.put("password", password);
          hostDetails.put("deviceName", deviceName);
          final Map<String, String> configParams = new HashMap<String, String>();
          UrlUtil.parseQueryParameters(uri.getQuery(), false, configParams);
          hostDetails.putAll(configParams);
  
          // let the server resource to do parameters validation
          try {
              resource.configure(guid, hostDetails);
          } catch (ConfigurationException e) {
              throw new CloudRuntimeException(e.getMessage());
          }
  
          final Host externalFirewall = _resourceMgr.addHost(zoneId, resource, Host.Type.ExternalFirewall, hostDetails);
          if (externalFirewall != null) {
              final PhysicalNetworkVO pNetworkFinal = pNetwork;
              return Transaction.execute(new TransactionCallback<ExternalFirewallDeviceVO>() {
                  @Override
                  public ExternalFirewallDeviceVO doInTransaction(TransactionStatus status) {
                      boolean dedicatedUse =
                          (configParams.get(ApiConstants.FIREWALL_DEVICE_DEDICATED) != null) ? Boolean.parseBoolean(configParams.get(ApiConstants.FIREWALL_DEVICE_DEDICATED))
                              : false;
                      long capacity = NumbersUtil.parseLong(configParams.get(ApiConstants.FIREWALL_DEVICE_CAPACITY), 0);
                      if (capacity == 0) {
                          capacity = _defaultFwCapacity;
                      }
  
                      ExternalFirewallDeviceVO fwDevice =
                          new ExternalFirewallDeviceVO(externalFirewall.getId(), pNetworkFinal.getId(), ntwkSvcProvider.getProviderName(), deviceName, capacity,
                              dedicatedUse);
  
                      _externalFirewallDeviceDao.persist(fwDevice);
  
                      DetailVO hostDetail = new DetailVO(externalFirewall.getId(), ApiConstants.FIREWALL_DEVICE_ID, String.valueOf(fwDevice.getId()));
                      _hostDetailDao.persist(hostDetail);
  
                      return fwDevice;
                  }
              });
          } else {
              return null;
          }
      }
  
      @Override
      public boolean deleteExternalFirewall(Long hostId) {
          HostVO externalFirewall = _hostDao.findById(hostId);
          if (externalFirewall == null) {
              throw new InvalidParameterValueException("Could not find an external firewall with ID: " + hostId);
          }
  
          DetailVO fwHostDetails = _hostDetailDao.findDetail(hostId, ApiConstants.FIREWALL_DEVICE_ID);
          long fwDeviceId = Long.parseLong(fwHostDetails.getValue());
  
          // check if any networks are using this balancer device
          List<NetworkExternalFirewallVO> networks = _networkExternalFirewallDao.listByFirewallDeviceId(fwDeviceId);
          if ((networks != null) && !networks.isEmpty()) {
              throw new CloudRuntimeException("Delete can not be done as there are networks using the firewall device ");
          }
  
          try {
              // put the host in maintenance state in order for it to be deleted
              externalFirewall.setResourceState(ResourceState.Maintenance);
              _hostDao.update(hostId, externalFirewall);
              _resourceMgr.deleteHost(hostId, false, false);
  
              // delete the external load balancer entry
              _externalFirewallDeviceDao.remove(fwDeviceId);
              return true;
          } catch (Exception e) {
              logger.debug("Failed to delete external firewall device due to " + e.getMessage());
              return false;
          }
      }
  
      @Override
      public List<Host> listExternalFirewalls(long physicalNetworkId, String deviceName) {
          List<Host> firewallHosts = new ArrayList<Host>();
          NetworkDevice fwNetworkDevice = NetworkDevice.getNetworkDevice(deviceName);
          PhysicalNetworkVO pNetwork = null;
  
          pNetwork = _physicalNetworkDao.findById(physicalNetworkId);
          if (pNetwork == null) {
              throw new InvalidParameterValueException("Could not find phyical network with ID: " + physicalNetworkId);
          }
  
          if ((pNetwork == null) || (fwNetworkDevice == null)) {
              throw new InvalidParameterValueException("Atleast one of ther required parameter physical networkId, device name is missing or invalid.");
          }
  
          PhysicalNetworkServiceProviderVO ntwkSvcProvider =
              _physicalNetworkServiceProviderDao.findByServiceProvider(pNetwork.getId(), fwNetworkDevice.getNetworkServiceProvder());
          if (ntwkSvcProvider == null) {
              return null;
          }
  
          List<ExternalFirewallDeviceVO> fwDevices = _externalFirewallDeviceDao.listByPhysicalNetworkAndProvider(physicalNetworkId, ntwkSvcProvider.getProviderName());
          for (ExternalFirewallDeviceVO fwDevice : fwDevices) {
              firewallHosts.add(_hostDao.findById(fwDevice.getHostId()));
          }
          return firewallHosts;
      }
  
      @Override
      public ExternalFirewallDeviceVO getExternalFirewallForNetwork(Network network) {
          NetworkExternalFirewallVO fwDeviceForNetwork = _networkExternalFirewallDao.findByNetworkId(network.getId());
          if (fwDeviceForNetwork != null) {
              long fwDeviceId = fwDeviceForNetwork.getExternalFirewallDeviceId();
              ExternalFirewallDeviceVO fwDevice = _externalFirewallDeviceDao.findById(fwDeviceId);
              assert (fwDevice != null);
              return fwDevice;
          }
          return null;
      }
  
      public void setExternalFirewallForNetwork(Network network, long externalFWDeviceID) {
          NetworkExternalFirewallVO fwDeviceForNetwork = new NetworkExternalFirewallVO(network.getId(), externalFWDeviceID);
          _networkExternalFirewallDao.persist(fwDeviceForNetwork);
      }
  
      @Override
      public ExternalFirewallDeviceVO findSuitableFirewallForNetwork(Network network) throws InsufficientCapacityException {
          long physicalNetworkId = network.getPhysicalNetworkId();
          List<ExternalFirewallDeviceVO> fwDevices = _externalFirewallDeviceDao.listByPhysicalNetwork(physicalNetworkId);
  
          // loop through the firewall device in the physical network and pick the first-fit
          for (ExternalFirewallDeviceVO fwDevice : fwDevices) {
              // max number of guest networks that can be mapped to this device
              long fullCapacity = fwDevice.getCapacity();
              if (fullCapacity == 0) {
                  fullCapacity = _defaultFwCapacity; // if capacity not configured then use the default
              }
  
              // get the list of guest networks that are mapped to this load balancer
              List<NetworkExternalFirewallVO> mappedNetworks = _networkExternalFirewallDao.listByFirewallDeviceId(fwDevice.getId());
  
              long usedCapacity = (mappedNetworks == null) ? 0 : mappedNetworks.size();
              if ((fullCapacity - usedCapacity) > 0) {
                  return fwDevice;
              }
          }
          throw new InsufficientNetworkCapacityException("Unable to find a firewall provider with sufficient capcity " + " to implement the network", DataCenter.class,
              network.getDataCenterId());
      }
  
      @DB
      protected boolean freeFirewallForNetwork(Network network) {
          GlobalLock deviceMapLock = GlobalLock.getInternLock("NetworkFirewallDeviceMap");
          try {
              if (deviceMapLock.lock(120)) {
                  try {
                      NetworkExternalFirewallVO fwDeviceForNetwork = _networkExternalFirewallDao.findByNetworkId(network.getId());
                      if (fwDeviceForNetwork != null) {
                          _networkExternalFirewallDao.remove(fwDeviceForNetwork.getId());
                      }
                  } catch (Exception exception) {
                      logger.error("Failed to release firewall device for the network {} due to {}", network, exception.getMessage());
                      return false;
                  } finally {
                      deviceMapLock.unlock();
                  }
              }
          } finally {
              deviceMapLock.releaseRef();
          }
          return true;
      }
  
      public String getExternalNetworkResourceGuid(long physicalNetworkId, String deviceName, String ip) {
          return physicalNetworkId + "-" + deviceName + "-" + ip;
      }
  
      public ExternalFirewallResponse createExternalFirewallResponse(Host externalFirewall) {
          Map<String, String> fwDetails = _hostDetailDao.findDetails(externalFirewall.getId());
          ExternalFirewallResponse response = new ExternalFirewallResponse();
          response.setId(externalFirewall.getUuid());
          response.setIpAddress(externalFirewall.getPrivateIpAddress());
          response.setUsername(fwDetails.get("username"));
          response.setPublicInterface(fwDetails.get("publicInterface"));
          response.setUsageInterface(fwDetails.get("usageInterface"));
          response.setPrivateInterface(fwDetails.get("privateInterface"));
          response.setPublicZone(fwDetails.get("publicZone"));
          response.setPrivateZone(fwDetails.get("privateZone"));
          response.setNumRetries(fwDetails.get("numRetries"));
          response.setTimeout(fwDetails.get("timeout"));
          return response;
      }
  
      @Override
      public boolean manageGuestNetworkWithExternalFirewall(boolean add, Network network) throws ResourceUnavailableException, InsufficientCapacityException {
          if (network.getTrafficType() != TrafficType.Guest) {
              logger.trace("External firewall can only be used for add/remove guest networks.");
              return false;
          }
  
          long zoneId = network.getDataCenterId();
          DataCenterVO zone = _dcDao.findById(zoneId);
          HostVO externalFirewall = null;
  
          if (add) {
              GlobalLock deviceMapLock = GlobalLock.getInternLock("NetworkFirewallDeviceMap");
              try {
                  if (deviceMapLock.lock(120)) {
                      try {
                          ExternalFirewallDeviceVO device = findSuitableFirewallForNetwork(network);
                          long externalFirewallId = device.getId();
  
                          NetworkExternalFirewallVO networkFW = new NetworkExternalFirewallVO(network.getId(), externalFirewallId);
                          _networkExternalFirewallDao.persist(networkFW);
  
                          externalFirewall = _hostDao.findById(device.getHostId());
                      } finally {
                          deviceMapLock.unlock();
                      }
                  }
              } finally {
                  deviceMapLock.releaseRef();
              }
          } else {
              ExternalFirewallDeviceVO fwDeviceVO = getExternalFirewallForNetwork(network);
              if (fwDeviceVO == null) {
                  logger.warn("Network shutdown requested on external firewall element, which did not implement the network."
                      + " Either network implement failed half way through or already network shutdown is completed.");
                  return true;
              }
              externalFirewall = _hostDao.findById(fwDeviceVO.getHostId());
          }
  
          Account account = _accountDao.findByIdIncludingRemoved(network.getAccountId());
  
          NetworkOffering offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
          boolean sharedSourceNat = offering.isSharedSourceNat();
  
          IPAddressVO sourceNatIp = null;
          if (!sharedSourceNat) {
              // Get the source NAT IP address for this network
              List<? extends IpAddress> sourceNatIps = _networkModel.listPublicIpsAssignedToAccount(network.getAccountId(), zoneId, true);
  
              for (IpAddress ipAddress : sourceNatIps) {
                  if (ipAddress.getAssociatedWithNetworkId().longValue() == network.getId()) {
                      sourceNatIp = _ipAddressDao.findById(ipAddress.getId());
                      break;
                  }
              }
              if (sourceNatIp == null) {
                  String errorMsg = "External firewall was unable to find the source NAT IP address for network " + network.getName();
                  logger.error(errorMsg);
                  return true;
              }
          }
  
          // Send a command to the external firewall to implement or shutdown the guest network
          long guestVlanTag = Long.parseLong(BroadcastDomainType.getValue(network.getBroadcastUri()));
          String guestVlanGateway = network.getGateway();
          String guestVlanCidr = network.getCidr();
          String sourceNatIpAddress = null;
          String publicVlanTag = null;
  
          if (sourceNatIp != null) {
              sourceNatIpAddress = sourceNatIp.getAddress().addr();
              VlanVO publicVlan = _vlanDao.findById(sourceNatIp.getVlanId());
              publicVlanTag = publicVlan.getVlanTag();
          }
  
          // Get network rate
          Integer networkRate = _networkModel.getNetworkRate(network.getId(), null);
  
          IpAddressTO ip = new IpAddressTO(account.getAccountId(), sourceNatIpAddress, add, false, !sharedSourceNat, publicVlanTag, null, null, null, networkRate, false);
          IpAddressTO[] ips = new IpAddressTO[1];
          ips[0] = ip;
          IpAssocCommand cmd = new IpAssocCommand(ips);
          cmd.setAccessDetail(NetworkElementCommand.GUEST_NETWORK_GATEWAY, guestVlanGateway);
          cmd.setAccessDetail(NetworkElementCommand.GUEST_NETWORK_CIDR, guestVlanCidr);
          cmd.setAccessDetail(NetworkElementCommand.GUEST_VLAN_TAG, String.valueOf(guestVlanTag));
          Answer answer = _agentMgr.easySend(externalFirewall.getId(), cmd);
  
          List<String> reservedIpAddressesForGuestNetwork = _nicDao.listIpAddressInNetwork(network.getId());
  
          if (answer == null || !answer.getResult()) {
              String action = add ? "implement" : "shutdown";
              String answerDetails = (answer != null) ? answer.getDetails() : "answer was null";
              String msg =
                  "External firewall was unable to " + action + " the guest network on the external firewall in zone " + zone.getName() + " due to " + answerDetails;
              logger.error(msg);
              if (!add && (!reservedIpAddressesForGuestNetwork.contains(network.getGateway()))) {
                  // If we failed the implementation as well, then just return, no complain
                  logger.error("Skip the shutdown of guest network on SRX because it seems we didn't implement it as well");
                  return true;
              }
              throw new ResourceUnavailableException(msg, DataCenter.class, zoneId);
          }
  
          if (add && (!reservedIpAddressesForGuestNetwork.contains(network.getGateway()))) {
              // Insert a new NIC for this guest network to reserve the gateway address
              _networkMgr.savePlaceholderNic(network, network.getGateway(), null, null);
          }
  
          // Delete any mappings used for inline external load balancers in this network
          List<NicVO> nicsInNetwork = _nicDao.listByNetworkId(network.getId());
          for (NicVO nic : nicsInNetwork) {
              InlineLoadBalancerNicMapVO mapping = _inlineLoadBalancerNicMapDao.findByNicId(nic.getId());
  
              if (mapping != null) {
                  _nicDao.expunge(mapping.getNicId());
                  _inlineLoadBalancerNicMapDao.expunge(mapping.getId());
              }
          }
  
          // on network shutdown, delete placeHolder nics used for the firewall device
          if (!add) {
              List<NicVO> nics = _nicDao.listByNetworkId(network.getId());
              for (NicVO nic : nics) {
                  if (nic.getVmType() == null && ReservationStrategy.PlaceHolder.equals(nic.getReservationStrategy()) && nic.getIPv4Address().equals(network.getGateway())) {
                      logger.debug("Removing placeholder nic " + nic + " for the network " + network);
                      _nicDao.remove(nic.getId());
                  }
              }
              freeFirewallForNetwork(network);
          }
  
          String action = add ? "implemented" : "shut down";
          logger.debug("External firewall has {} the guest network for account {} with VLAN tag {}", action, account, guestVlanTag);
  
          return true;
      }
  
      @Override
      public boolean applyFirewallRules(Network network, List<? extends FirewallRule> rules) throws ResourceUnavailableException {
          // Find the external firewall in this zone
          long zoneId = network.getDataCenterId();
          DataCenterVO zone = _dcDao.findById(zoneId);
          ExternalFirewallDeviceVO fwDeviceVO = getExternalFirewallForNetwork(network);
          // During destroy, device reference may already been clean up, then we just return true
          if (fwDeviceVO == null) {
              return true;
          }
          HostVO externalFirewall = _hostDao.findById(fwDeviceVO.getHostId());
  
          assert (externalFirewall != null);
  
          if (network.getState() == Network.State.Allocated) {
              logger.debug("External firewall was asked to apply firewall rules for network {}; " +
                      "this network is not implemented. Skipping backend commands.", network);
              return true;
          }
  
          List<FirewallRuleTO> rulesTO = new ArrayList<FirewallRuleTO>();
          NetworkVO networkVO = _networkDao.findById(network.getId());
          NetworkOfferingVO offering = _networkOfferingDao.findById(networkVO.getNetworkOfferingId());
          Boolean defaultEgressPolicy = offering.isEgressDefaultPolicy();
  
          for (FirewallRule rule : rules) {
              if (rule.getSourceCidrList() == null && (rule.getPurpose() == Purpose.Firewall || rule.getPurpose() == Purpose.NetworkACL)) {
                  _fwRulesDao.loadSourceCidrs((FirewallRuleVO)rule);
              }
              FirewallRuleTO ruleTO;
              if (rule.getPurpose() == Purpose.Firewall && rule.getTrafficType() == FirewallRule.TrafficType.Egress) {
                  String guestVlanTag = BroadcastDomainType.getValue(network.getBroadcastUri());
                  String guestCidr = network.getCidr();
                  ruleTO = new FirewallRuleTO(rule, guestVlanTag, rule.getTrafficType(), guestCidr, defaultEgressPolicy, rule.getType());
              } else {
                  IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId());
                  Vlan vlan = _vlanDao.findById(sourceIp.getVlanId());
  
                  ruleTO = new FirewallRuleTO(rule, vlan.getVlanTag(), sourceIp.getAddress().addr());
              }
              rulesTO.add(ruleTO);
          }
  
          //Firewall rules configured for staticNAT/PF
          sendFirewallRules(rulesTO, zone, externalFirewall.getId());
  
          return true;
      }
  
      public boolean applyStaticNatRules(Network network, List<? extends StaticNat> rules) throws ResourceUnavailableException {
          long zoneId = network.getDataCenterId();
          DataCenterVO zone = _dcDao.findById(zoneId);
          ExternalFirewallDeviceVO fwDeviceVO = getExternalFirewallForNetwork(network);
          HostVO externalFirewall = _hostDao.findById(fwDeviceVO.getHostId());
  
          assert (externalFirewall != null);
  
          if (network.getState() == Network.State.Allocated) {
              logger.debug("External firewall was asked to apply firewall rules for network {}; " +
                      "this network is not implemented. Skipping backend commands.", network);
              return true;
          }
  
          List<StaticNatRuleTO> staticNatRules = new ArrayList<StaticNatRuleTO>();
  
          for (StaticNat rule : rules) {
              IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId());
              Vlan vlan = _vlanDao.findById(sourceIp.getVlanId());
  
              StaticNatRuleTO ruleTO =
                  new StaticNatRuleTO(0, vlan.getVlanTag(), sourceIp.getAddress().addr(), -1, -1, rule.getDestIpAddress(), -1, -1, "any", rule.isForRevoke(), false);
              staticNatRules.add(ruleTO);
          }
  
          sendStaticNatRules(staticNatRules, zone, externalFirewall.getId());
  
          return true;
      }
  
      protected void sendFirewallRules(List<FirewallRuleTO> firewallRules, DataCenter zone, long externalFirewallId) throws ResourceUnavailableException {
          if (!firewallRules.isEmpty()) {
              SetFirewallRulesCommand cmd = new SetFirewallRulesCommand(firewallRules);
              Answer answer = _agentMgr.easySend(externalFirewallId, cmd);
              if (answer == null || !answer.getResult()) {
                  String details = (answer != null) ? answer.getDetails() : "details unavailable";
                  String msg = "External firewall was unable to apply static nat rules to the SRX appliance in zone " + zone.getName() + " due to: " + details + ".";
                  logger.error(msg);
                  throw new ResourceUnavailableException(msg, DataCenter.class, zone.getId());
              }
          }
      }
  
      protected void sendStaticNatRules(List<StaticNatRuleTO> staticNatRules, DataCenter zone, long externalFirewallId) throws ResourceUnavailableException {
          if (!staticNatRules.isEmpty()) {
              SetStaticNatRulesCommand cmd = new SetStaticNatRulesCommand(staticNatRules, null);
              Answer answer = _agentMgr.easySend(externalFirewallId, cmd);
              if (answer == null || !answer.getResult()) {
                  String details = (answer != null) ? answer.getDetails() : "details unavailable";
                  String msg = "External firewall was unable to apply static nat rules to the SRX appliance in zone " + zone.getName() + " due to: " + details + ".";
                  logger.error(msg);
                  throw new ResourceUnavailableException(msg, DataCenter.class, zone.getId());
              }
          }
      }
  
      protected void sendPortForwardingRules(List<PortForwardingRuleTO> portForwardingRules, DataCenter zone, long externalFirewallId) throws ResourceUnavailableException {
          if (!portForwardingRules.isEmpty()) {
              SetPortForwardingRulesCommand cmd = new SetPortForwardingRulesCommand(portForwardingRules);
              Answer answer = _agentMgr.easySend(externalFirewallId, cmd);
              if (answer == null || !answer.getResult()) {
                  String details = (answer != null) ? answer.getDetails() : "details unavailable";
                  String msg = "External firewall was unable to apply port forwarding rules to the SRX appliance in zone " + zone.getName() + " due to: " + details + ".";
                  logger.error(msg);
                  throw new ResourceUnavailableException(msg, DataCenter.class, zone.getId());
              }
          }
      }
  
      public boolean applyIps(Network network, List<? extends PublicIpAddress> ipAddresses) throws ResourceUnavailableException {
          return true;
      }
  
      public boolean manageRemoteAccessVpn(boolean create, Network network, RemoteAccessVpn vpn) throws ResourceUnavailableException {
          ExternalFirewallDeviceVO fwDeviceVO = getExternalFirewallForNetwork(network);
          HostVO externalFirewall = _hostDao.findById(fwDeviceVO.getHostId());
  
          if (externalFirewall == null) {
              return false;
          }
  
          // Create/delete VPN
          IpAddress ip = _networkModel.getIp(vpn.getServerAddressId());
  
          // Mask the IP range with the network's VLAN tag
          String[] ipRange = vpn.getIpRange().split("-");
          DataCenterVO zone = _dcDao.findById(network.getDataCenterId());
          int vlanTag = Integer.parseInt(BroadcastDomainType.getValue(network.getBroadcastUri()));
          int offset = getVlanOffset(network.getPhysicalNetworkId(), vlanTag);
          int cidrSize = getGloballyConfiguredCidrSize();
  
          for (int i = 0; i < 2; i++) {
              ipRange[i] = NetUtils.long2Ip((NetUtils.ip2Long(ipRange[i]) & 0xff000000) | (offset << (32 - cidrSize)));
          }
  
          String maskedIpRange = ipRange[0] + "-" + ipRange[1];
  
          RemoteAccessVpnCfgCommand createVpnCmd =
              new RemoteAccessVpnCfgCommand(create, ip.getAddress().addr(), vpn.getLocalIp(), maskedIpRange, vpn.getIpsecPresharedKey(), false);
          createVpnCmd.setAccessDetail(NetworkElementCommand.ACCOUNT_ID, String.valueOf(network.getAccountId()));
          createVpnCmd.setAccessDetail(NetworkElementCommand.GUEST_NETWORK_CIDR, network.getCidr());
          Answer answer = _agentMgr.easySend(externalFirewall.getId(), createVpnCmd);
          if (answer == null || !answer.getResult()) {
              String details = (answer != null) ? answer.getDetails() : "details unavailable";
              String msg = "External firewall was unable to create a remote access VPN in zone " + zone.getName() + " due to: " + details + ".";
              logger.error(msg);
              throw new ResourceUnavailableException(msg, DataCenter.class, zone.getId());
          }
  
          // Add/delete users
          List<VpnUserVO> vpnUsers = _vpnUsersDao.listByAccount(vpn.getAccountId());
          return manageRemoteAccessVpnUsers(network, vpn, vpnUsers);
      }
  
      public boolean manageRemoteAccessVpnUsers(Network network, RemoteAccessVpn vpn, List<? extends VpnUser> vpnUsers) throws ResourceUnavailableException {
          ExternalFirewallDeviceVO fwDeviceVO = getExternalFirewallForNetwork(network);
          HostVO externalFirewall = _hostDao.findById(fwDeviceVO.getHostId());
  
          if (externalFirewall == null) {
              return false;
          }
  
          List<VpnUser> addUsers = new ArrayList<VpnUser>();
          List<VpnUser> removeUsers = new ArrayList<VpnUser>();
          for (VpnUser user : vpnUsers) {
              if (user.getState() == VpnUser.State.Add || user.getState() == VpnUser.State.Active) {
                  addUsers.add(user);
              } else if (user.getState() == VpnUser.State.Revoke) {
                  removeUsers.add(user);
              }
          }
  
          VpnUsersCfgCommand addUsersCmd = new VpnUsersCfgCommand(addUsers, removeUsers);
          addUsersCmd.setAccessDetail(NetworkElementCommand.ACCOUNT_ID, String.valueOf(network.getAccountId()));
          addUsersCmd.setAccessDetail(NetworkElementCommand.GUEST_NETWORK_CIDR, network.getCidr());
  
          Answer answer = _agentMgr.easySend(externalFirewall.getId(), addUsersCmd);
          if (answer == null || !answer.getResult()) {
              String details = (answer != null) ? answer.getDetails() : "details unavailable";
              DataCenterVO zone = _dcDao.findById(network.getDataCenterId());
              String msg = "External firewall was unable to add remote access users in zone " + zone.getName() + " due to: " + details + ".";
              logger.error(msg);
              throw new ResourceUnavailableException(msg, DataCenter.class, zone.getId());
          }
  
          return true;
      }
  
      public int getVlanOffset(long physicalNetworkId, int vlanTag) {
          PhysicalNetworkVO pNetwork = _physicalNetworkDao.findById(physicalNetworkId);
          if (pNetwork == null) {
              throw new CloudRuntimeException("Could not find the physical Network " + physicalNetworkId + ".");
          }
  
          if (pNetwork.getVnet() == null) {
              throw new CloudRuntimeException("Could not find vlan range for physical Network " + pNetwork + ".");
          }
          Integer lowestVlanTag = null;
          List<Pair<Integer, Integer>> vnetList = pNetwork.getVnet();
          //finding the vlanrange in which the vlanTag lies.
          for (Pair<Integer, Integer> vnet : vnetList) {
              if (vlanTag >= vnet.first() && vlanTag <= vnet.second()) {
                  lowestVlanTag = vnet.first();
              }
          }
          if (lowestVlanTag == null) {
              throw new InvalidParameterValueException("The vlan tag does not belong to any of the existing vlan ranges");
          }
          return vlanTag - lowestVlanTag;
      }
  
      public int getGloballyConfiguredCidrSize() {
          try {
              String globalVlanBits = _configDao.getValue(Config.GuestVlanBits.key());
              return 8 + Integer.parseInt(globalVlanBits);
          } catch (Exception e) {
              throw new CloudRuntimeException("Failed to read the globally configured VLAN bits size.");
          }
      }
  
      @Override
      public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) {
          // TODO Auto-generated method stub
          return null;
      }
  
      @Override
      public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map<String, String> details, List<String> hostTags) {
          if (!(startup[0] instanceof StartupExternalFirewallCommand)) {
              return null;
          }
          host.setType(Host.Type.ExternalFirewall);
          return host;
      }
  
      @Override
      public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException {
          if (host.getType() != com.cloud.host.Host.Type.ExternalFirewall) {
              return null;
          }
          return new DeleteHostAnswer(true);
      }
  
      @Override
      public boolean applyPortForwardingRules(Network network, List<? extends PortForwardingRule> rules) throws ResourceUnavailableException {
          // Find the external firewall in this zone
          long zoneId = network.getDataCenterId();
          DataCenterVO zone = _dcDao.findById(zoneId);
          ExternalFirewallDeviceVO fwDeviceVO = getExternalFirewallForNetwork(network);
          HostVO externalFirewall = _hostDao.findById(fwDeviceVO.getHostId());
  
          assert (externalFirewall != null);
  
          if (network.getState() == Network.State.Allocated) {
              logger.debug("External firewall was asked to apply firewall rules for network {}; " +
                      "this network is not implemented. Skipping backend commands.", network);
              return true;
          }
  
          List<PortForwardingRuleTO> pfRules = new ArrayList<PortForwardingRuleTO>();
  
          for (PortForwardingRule rule : rules) {
              IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId());
              Vlan vlan = _vlanDao.findById(sourceIp.getVlanId());
  
              PortForwardingRuleTO ruleTO = new PortForwardingRuleTO(rule, vlan.getVlanTag(), sourceIp.getAddress().addr());
              pfRules.add(ruleTO);
          }
  
          sendPortForwardingRules(pfRules, zone, externalFirewall.getId());
          return true;
      }
  }
  