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 java.util.concurrent.Executors;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.concurrent.TimeUnit;
  
  import javax.inject.Inject;
  import javax.naming.ConfigurationException;
  
  import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
  import org.apache.cloudstack.managed.context.ManagedContextRunnable;
  import org.springframework.stereotype.Component;
  
  import com.cloud.agent.AgentManager;
  import com.cloud.agent.api.ExternalNetworkResourceUsageAnswer;
  import com.cloud.agent.api.ExternalNetworkResourceUsageCommand;
  import com.cloud.configuration.Config;
  import com.cloud.dc.DataCenterVO;
  import com.cloud.dc.dao.DataCenterDao;
  import com.cloud.dc.dao.HostPodDao;
  import com.cloud.dc.dao.VlanDao;
  import com.cloud.host.Host;
  import com.cloud.host.HostVO;
  import com.cloud.host.Host.Type;
  import com.cloud.host.dao.HostDao;
  import com.cloud.host.dao.HostDetailsDao;
  import com.cloud.network.Networks.BroadcastDomainType;
  import com.cloud.network.dao.ExternalFirewallDeviceDao;
  import com.cloud.network.dao.ExternalFirewallDeviceVO;
  import com.cloud.network.dao.ExternalLoadBalancerDeviceDao;
  import com.cloud.network.dao.ExternalLoadBalancerDeviceVO;
  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.LoadBalancerVO;
  import com.cloud.network.dao.NetworkDao;
  import com.cloud.network.dao.NetworkExternalFirewallDao;
  import com.cloud.network.dao.NetworkExternalFirewallVO;
  import com.cloud.network.dao.NetworkExternalLoadBalancerDao;
  import com.cloud.network.dao.NetworkExternalLoadBalancerVO;
  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.rules.LoadBalancerContainer.Scheme;
  import com.cloud.network.rules.PortForwardingRuleVO;
  import com.cloud.network.rules.dao.PortForwardingRulesDao;
  import com.cloud.offering.NetworkOffering;
  import com.cloud.offerings.dao.NetworkOfferingDao;
  import com.cloud.resource.ResourceManager;
  import com.cloud.user.AccountManager;
  import com.cloud.user.AccountVO;
  import com.cloud.user.UserStatisticsVO;
  import com.cloud.user.dao.AccountDao;
  import com.cloud.user.dao.UserStatisticsDao;
  import com.cloud.utils.NumbersUtil;
  import com.cloud.utils.component.ManagerBase;
  import com.cloud.utils.concurrency.NamedThreadFactory;
  import com.cloud.utils.db.GlobalLock;
  import com.cloud.utils.db.Transaction;
  import com.cloud.utils.db.TransactionCallbackNoReturn;
  import com.cloud.utils.db.TransactionStatus;
  import com.cloud.utils.exception.CloudRuntimeException;
  import com.cloud.vm.DomainRouterVO;
  import com.cloud.vm.NicVO;
  import com.cloud.vm.dao.DomainRouterDao;
  import com.cloud.vm.dao.NicDao;
  
  import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
  
  @Component
  public class ExternalDeviceUsageManagerImpl extends ManagerBase implements ExternalDeviceUsageManager {
  
      String _name;
      @Inject
      NetworkExternalLoadBalancerDao _networkExternalLBDao;
      @Inject
      ExternalLoadBalancerDeviceDao _externalLoadBalancerDeviceDao;
      @Inject
      HostDao _hostDao;
      @Inject
      DataCenterDao _dcDao;
      @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
      HostDetailsDao _hostDetailDao;
      @Inject
      NetworkExternalLoadBalancerDao _networkLBDao;
      @Inject
      NetworkServiceMapDao _ntwkSrvcProviderDao;
      @Inject
      NetworkExternalFirewallDao _networkExternalFirewallDao;
      @Inject
      ExternalFirewallDeviceDao _externalFirewallDeviceDao;
      @Inject
      protected HostPodDao _podDao = null;
      @Inject
      NetworkModel _networkModel;
  
      ScheduledExecutorService _executor;
      private int _externalNetworkStatsInterval;
  
      @Override
      public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
          _externalNetworkStatsInterval = NumbersUtil.parseInt(_configDao.getValue(Config.ExternalNetworkStatsInterval.key()), 300);
          if (_externalNetworkStatsInterval > 0) {
              _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("ExternalNetworkMonitor"));
          }
          return true;
  
      }
  
      @Override
      public boolean start() {
          if (_externalNetworkStatsInterval > 0) {
              _executor.scheduleAtFixedRate(new ExternalDeviceNetworkUsageTask(), _externalNetworkStatsInterval, _externalNetworkStatsInterval, TimeUnit.SECONDS);
          }
          return true;
      }
  
      @Override
      public boolean stop() {
          return true;
      }
  
      @Override
      public String getName() {
          return _name;
      }
  
      private ExternalLoadBalancerDeviceVO getExternalLoadBalancerForNetwork(Network network) {
          NetworkExternalLoadBalancerVO lbDeviceForNetwork = _networkExternalLBDao.findByNetworkId(network.getId());
          if (lbDeviceForNetwork != null) {
              long lbDeviceId = lbDeviceForNetwork.getExternalLBDeviceId();
              ExternalLoadBalancerDeviceVO lbDeviceVo = _externalLoadBalancerDeviceDao.findById(lbDeviceId);
              assert (lbDeviceVo != null);
              return lbDeviceVo;
          }
          return null;
      }
  
      private 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;
      }
  
      @Override
      public void updateExternalLoadBalancerNetworkUsageStats(long loadBalancerRuleId) {
  
          LoadBalancerVO lb = _loadBalancerDao.findById(loadBalancerRuleId);
          if (lb == null) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Cannot update usage stats, LB rule is not found");
              }
              return;
          }
          long networkId = lb.getNetworkId();
          Network network = _networkDao.findById(networkId);
          if (network == null) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Cannot update usage stats, Network is not found");
              }
              return;
          }
  
          ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network);
          if (lbDeviceVO == null) {
              if (logger.isDebugEnabled()) {
                  logger.debug("Cannot update usage stats,  No external LB device found");
              }
              return;
          }
  
          // Get network stats from the external load balancer
          ExternalNetworkResourceUsageAnswer lbAnswer = null;
          HostVO externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId());
          if (externalLoadBalancer != null) {
              ExternalNetworkResourceUsageCommand cmd = new ExternalNetworkResourceUsageCommand();
              lbAnswer = (ExternalNetworkResourceUsageAnswer)_agentMgr.easySend(externalLoadBalancer.getId(), cmd);
              if (lbAnswer == null || !lbAnswer.getResult()) {
                  String details = (lbAnswer != null) ? lbAnswer.getDetails() : "details unavailable";
                  String msg = String.format("Unable to get external load balancer stats for network %s due to: %s.", network, details);
                  logger.error(msg);
                  return;
              }
          }
  
          long accountId = lb.getAccountId();
          AccountVO account = _accountDao.findById(accountId);
          if (account == null) {
              logger.debug("Skipping stats update for external LB for account with ID " + accountId);
              return;
          }
  
          String publicIp = _networkModel.getIp(lb.getSourceIpAddressId()).getAddress().addr();
          DataCenterVO zone = _dcDao.findById(network.getDataCenterId());
          String statsEntryIdentifier =
              "account " + account.getAccountName() + ", zone " + zone.getName() + ", network ID " + networkId + ", host ID " + externalLoadBalancer.getName();
  
          long newCurrentBytesSent = 0;
          long newCurrentBytesReceived = 0;
  
          if (publicIp != null) {
              long[] bytesSentAndReceived = null;
              statsEntryIdentifier += ", public IP: " + publicIp;
              boolean inline = _networkModel.isNetworkInlineMode(network);
              if (externalLoadBalancer.getType().equals(Host.Type.ExternalLoadBalancer) && inline) {
                  // Look up stats for the guest IP address that's mapped to the public IP address
                  InlineLoadBalancerNicMapVO mapping = _inlineLoadBalancerNicMapDao.findByPublicIpAddress(publicIp);
  
                  if (mapping != null) {
                      NicVO nic = _nicDao.findById(mapping.getNicId());
                      String loadBalancingIpAddress = nic.getIPv4Address();
                      bytesSentAndReceived = lbAnswer.ipBytes.get(loadBalancingIpAddress);
  
                      if (bytesSentAndReceived != null) {
                          bytesSentAndReceived[0] = 0;
                      }
                  }
              } else {
                  bytesSentAndReceived = lbAnswer.ipBytes.get(publicIp);
              }
  
              if (bytesSentAndReceived == null) {
                  logger.debug("Didn't get an external network usage answer for public IP " + publicIp);
              } else {
                  newCurrentBytesSent += bytesSentAndReceived[0];
                  newCurrentBytesReceived += bytesSentAndReceived[1];
              }
  
              commitStats(networkId, externalLoadBalancer, accountId, publicIp, zone, statsEntryIdentifier, newCurrentBytesSent, newCurrentBytesReceived);
          }
      }
  
      private void commitStats(final long networkId, final HostVO externalLoadBalancer, final long accountId, final String publicIp, final DataCenterVO zone,
          final String statsEntryIdentifier, final long newCurrentBytesSent, final long newCurrentBytesReceived) {
          Transaction.execute(new TransactionCallbackNoReturn() {
              @Override
              public void doInTransactionWithoutResult(TransactionStatus status) {
                  UserStatisticsVO userStats;
                  userStats = _userStatsDao.lock(accountId, zone.getId(), networkId, publicIp, externalLoadBalancer.getId(), externalLoadBalancer.getType().toString());
  
                  if (userStats != null) {
                      long oldNetBytesSent = userStats.getNetBytesSent();
                      long oldNetBytesReceived = userStats.getNetBytesReceived();
                      long oldCurrentBytesSent = userStats.getCurrentBytesSent();
                      long oldCurrentBytesReceived = userStats.getCurrentBytesReceived();
                      String warning =
                          "Received an external network stats byte count that was less than the stored value. Zone ID: " + userStats.getDataCenterId() + ", account ID: " +
                              userStats.getAccountId() + ".";
  
                      userStats.setCurrentBytesSent(newCurrentBytesSent);
                      if (oldCurrentBytesSent > newCurrentBytesSent) {
                          logger.warn(warning + "Stored bytes sent: " + toHumanReadableSize(oldCurrentBytesSent) + ", new bytes sent: " + toHumanReadableSize(newCurrentBytesSent) + ".");
                          userStats.setNetBytesSent(oldNetBytesSent + oldCurrentBytesSent);
                      }
  
                      userStats.setCurrentBytesReceived(newCurrentBytesReceived);
                      if (oldCurrentBytesReceived > newCurrentBytesReceived) {
                          logger.warn(warning + "Stored bytes received: " + toHumanReadableSize(oldCurrentBytesReceived) + ", new bytes received: " + toHumanReadableSize(newCurrentBytesReceived) + ".");
                          userStats.setNetBytesReceived(oldNetBytesReceived + oldCurrentBytesReceived);
                      }
  
                      if (_userStatsDao.update(userStats.getId(), userStats)) {
                          logger.debug("Successfully updated stats for " + statsEntryIdentifier);
                      } else {
                          logger.debug("Failed to update stats for " + statsEntryIdentifier);
                      }
                  } else {
                      logger.warn("Unable to find user stats entry for " + statsEntryIdentifier);
                  }
              }
          });
      }
  
      public boolean isNccServiceProvider(Network network) {
          NetworkOffering networkOffering = _networkOfferingDao.findById(network.getNetworkOfferingId());
          if(null!= networkOffering && networkOffering.getServicePackage() != null ) {
              return true;
          }
          else {
              return false;
          }
      }
  
      public HostVO getNetScalerControlCenterForNetwork(Network guestConfig) {
          long zoneId = guestConfig.getDataCenterId();
          return _hostDao.findByTypeNameAndZoneId(zoneId, "NetscalerControlCenter", Type.NetScalerControlCenter);
      }
  
      protected class ExternalDeviceNetworkUsageTask extends ManagedContextRunnable {
  
          public ExternalDeviceNetworkUsageTask() {
  
          }
  
          @Override
          protected void runInContext() {
  
              // Check if there are any external devices
              // Skip external device usage collection if none exist
  
              if(_hostDao.listByType(Host.Type.ExternalFirewall).isEmpty() && _hostDao.listByType(Host.Type.ExternalLoadBalancer).isEmpty()){
                  logger.debug("External devices are not used. Skipping external device usage collection");
                  return;
              }
  
              GlobalLock scanLock = GlobalLock.getInternLock("ExternalDeviceNetworkUsageManagerImpl");
              try {
                  if (scanLock.lock(20)) {
                      try {
                          runExternalDeviceNetworkUsageTask();
                      } finally {
                          scanLock.unlock();
                      }
                  }
              } catch (Exception e) {
                  logger.warn("Problems while getting external device usage", e);
              } finally {
                  scanLock.releaseRef();
              }
          }
  
          protected void runExternalDeviceNetworkUsageTask() {
              logger.debug("External devices stats collector is running...");
  
              for (DataCenterVO zone : _dcDao.listAll()) {
                  List<DomainRouterVO> domainRoutersInZone = _routerDao.listByDataCenter(zone.getId());
                  if (domainRoutersInZone == null) {
                      continue;
                  }
                  Map<Long, ExternalNetworkResourceUsageAnswer> lbDeviceUsageAnswerMap = new HashMap<Long, ExternalNetworkResourceUsageAnswer>();
                  Map<Long, ExternalNetworkResourceUsageAnswer> fwDeviceUsageAnswerMap = new HashMap<Long, ExternalNetworkResourceUsageAnswer>();
                  List<Long> accountsProcessed = new ArrayList<Long>();
  
                  for (DomainRouterVO domainRouter : domainRoutersInZone) {
                      long accountId = domainRouter.getAccountId();
  
                      if (accountsProcessed.contains(new Long(accountId))) {
                          if (logger.isTraceEnabled()) {
                              logger.trace("Networks for Account " + accountId + " are already processed for external network usage, so skipping usage check.");
                          }
                          continue;
                      }
  
                      long zoneId = zone.getId();
  
                      List<NetworkVO> networksForAccount = _networkDao.listByZoneAndGuestType(accountId, zoneId, Network.GuestType.Isolated, false);
                      if (networksForAccount == null) {
                          continue;
                      }
  
                      for (NetworkVO network : networksForAccount) {
                          if (!_networkModel.networkIsConfiguredForExternalNetworking(zoneId, network.getId())) {
                              logger.debug("Network {} is not configured for external networking, so skipping usage check.", network);
                              continue;
                          }
  
                          ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network);
                          HostVO externalNcc = null;
                          boolean isNccNetwork = isNccServiceProvider(network);
                          if(isNccNetwork) {
                              externalNcc = getNetScalerControlCenterForNetwork(network);
                          }
                          ExternalFirewallDeviceVO fwDeviceVO = getExternalFirewallForNetwork(network);
  
                          if (fwDeviceVO == null) {
                              continue;
                          }
                          if(externalNcc == null && lbDeviceVO == null) {
                              return;
                          }
  
                          // Get network stats from the external firewall
                          ExternalNetworkResourceUsageAnswer firewallAnswer = null;
                          HostVO externalFirewall = null;
                          if (fwDeviceVO != null) {
                              externalFirewall = _hostDao.findById(fwDeviceVO.getHostId());
                              if (externalFirewall != null) {
                                  Long fwDeviceId = new Long(externalFirewall.getId());
                                  if (!fwDeviceUsageAnswerMap.containsKey(fwDeviceId)) {
                                      try {
                                          ExternalNetworkResourceUsageCommand cmd = new ExternalNetworkResourceUsageCommand();
                                          firewallAnswer = (ExternalNetworkResourceUsageAnswer)_agentMgr.easySend(externalFirewall.getId(), cmd);
                                          if (firewallAnswer == null || !firewallAnswer.getResult()) {
                                              String details = (firewallAnswer != null) ? firewallAnswer.getDetails() : "details unavailable";
                                              String msg = "Unable to get external firewall stats for network" + zone.getName() + " due to: " + details + ".";
                                              logger.error(msg);
                                          } else {
                                              fwDeviceUsageAnswerMap.put(fwDeviceId, firewallAnswer);
                                          }
                                      } catch (Exception e) {
                                          String msg = "Unable to get external firewall stats for network" + zone.getName();
                                          logger.error(msg, e);
                                      }
                                  } else {
                                      if (logger.isTraceEnabled()) {
                                          logger.trace("Reusing usage Answer for device id {} for Network {}", fwDeviceId, network);
                                      }
                                      firewallAnswer = fwDeviceUsageAnswerMap.get(fwDeviceId);
                                  }
                              }
                          }
  
                          // Get network stats from the external load balancer
                          ExternalNetworkResourceUsageAnswer lbAnswer = null;
                          HostVO externalLoadBalancer = null;
                          if (lbDeviceVO != null || externalNcc != null) {
                              if(isNccNetwork) {
                                  externalLoadBalancer = externalNcc;
                              } else {
                                  externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId());
                              }
                              if (externalLoadBalancer != null) {
                                  Long lbDeviceId = new Long(externalLoadBalancer.getId());
                                  if (!lbDeviceUsageAnswerMap.containsKey(lbDeviceId)) {
                                      try {
                                          ExternalNetworkResourceUsageCommand cmd = new ExternalNetworkResourceUsageCommand(network.getId());
                                          lbAnswer = (ExternalNetworkResourceUsageAnswer)_agentMgr.easySend(externalLoadBalancer.getId(), cmd);
                                          if (lbAnswer == null || !lbAnswer.getResult()) {
                                              String details = (lbAnswer != null) ? lbAnswer.getDetails() : "details unavailable";
                                              String msg = "Unable to get external load balancer stats for " + zone.getName() + " due to: " + details + ".";
                                              logger.error(msg);
                                          } else {
                                              lbDeviceUsageAnswerMap.put(lbDeviceId, lbAnswer);
                                          }
                                      } catch (Exception e) {
                                          String msg = "Unable to get external load balancer stats for " + zone.getName();
                                          logger.error(msg, e);
                                      }
                                  } else {
                                      if (logger.isTraceEnabled()) {
                                          logger.trace("Reusing usage Answer for device id {} for Network {}", lbDeviceId, network);
                                      }
                                      lbAnswer = lbDeviceUsageAnswerMap.get(lbDeviceId);
                                  }
                              }
                          }
  
                          if (firewallAnswer == null && lbAnswer == null) {
                              continue;
                          }
  
                          AccountVO account = _accountDao.findById(accountId);
                          if (account == null) {
                              logger.debug("Skipping stats update for account with ID " + accountId);
                              continue;
                          }
  
                          if (!manageStatsEntries(true, accountId, zoneId, network, externalFirewall, firewallAnswer, externalLoadBalancer, lbAnswer)) {
                              continue;
                          }
  
                          manageStatsEntries(false, accountId, zoneId, network, externalFirewall, firewallAnswer, externalLoadBalancer, lbAnswer);
                      }
  
                      accountsProcessed.add(new Long(accountId));
                  }
              }
          }
  
          private boolean updateBytes(UserStatisticsVO userStats, long newCurrentBytesSent, long newCurrentBytesReceived) {
              long oldNetBytesSent = userStats.getNetBytesSent();
              long oldNetBytesReceived = userStats.getNetBytesReceived();
              long oldCurrentBytesSent = userStats.getCurrentBytesSent();
              long oldCurrentBytesReceived = userStats.getCurrentBytesReceived();
              String warning =
                  "Received an external network stats byte count that was less than the stored value. Zone ID: " + userStats.getDataCenterId() + ", account ID: " +
                      userStats.getAccountId() + ".";
  
              userStats.setCurrentBytesSent(newCurrentBytesSent);
              if (oldCurrentBytesSent > newCurrentBytesSent) {
                  logger.warn(warning + "Stored bytes sent: " + toHumanReadableSize(oldCurrentBytesSent) + ", new bytes sent: " + toHumanReadableSize(newCurrentBytesSent) + ".");
                  userStats.setNetBytesSent(oldNetBytesSent + oldCurrentBytesSent);
              }
  
              userStats.setCurrentBytesReceived(newCurrentBytesReceived);
              if (oldCurrentBytesReceived > newCurrentBytesReceived) {
                  logger.warn(warning + "Stored bytes received: " + toHumanReadableSize(oldCurrentBytesReceived) + ", new bytes received: " + toHumanReadableSize(newCurrentBytesReceived) + ".");
                  userStats.setNetBytesReceived(oldNetBytesReceived + oldCurrentBytesReceived);
              }
  
              return _userStatsDao.update(userStats.getId(), userStats);
          }
  
          // Creates a new stats entry for the specified parameters, if one doesn't already exist.
          private boolean createStatsEntry(long accountId, long zoneId, long networkId, String publicIp, long hostId) {
              HostVO host = _hostDao.findById(hostId);
              UserStatisticsVO userStats = _userStatsDao.findBy(accountId, zoneId, networkId, publicIp, hostId, host.getType().toString());
              if (userStats == null) {
                  return (_userStatsDao.persist(new UserStatisticsVO(accountId, zoneId, publicIp, hostId, host.getType().toString(), networkId)) != null);
              } else {
                  return true;
              }
          }
  
          // Updates an existing stats entry with new data from the specified usage answer.
          private boolean updateStatsEntry(long accountId, long zoneId, long networkId, String publicIp, long hostId, ExternalNetworkResourceUsageAnswer answer,
              boolean inline) {
              AccountVO account = _accountDao.findById(accountId);
              DataCenterVO zone = _dcDao.findById(zoneId);
              NetworkVO network = _networkDao.findById(networkId);
              HostVO host = _hostDao.findById(hostId);
              String statsEntryIdentifier =
                  "account " + account.getAccountName() + ", zone " + zone.getName() + ", network ID " + networkId + ", host ID " + host.getName();
  
              long newCurrentBytesSent = 0;
              long newCurrentBytesReceived = 0;
  
              if (publicIp != null) {
                  long[] bytesSentAndReceived = null;
                  statsEntryIdentifier += ", public IP: " + publicIp;
  
                  if (host.getType().equals(Host.Type.ExternalLoadBalancer) && inline) {
                      // Look up stats for the guest IP address that's mapped to the public IP address
                      InlineLoadBalancerNicMapVO mapping = _inlineLoadBalancerNicMapDao.findByPublicIpAddress(publicIp);
  
                      if (mapping != null) {
                          NicVO nic = _nicDao.findById(mapping.getNicId());
                          String loadBalancingIpAddress = nic.getIPv4Address();
                          bytesSentAndReceived = answer.ipBytes.get(loadBalancingIpAddress);
  
                          if (bytesSentAndReceived != null) {
                              bytesSentAndReceived[0] = 0;
                          }
                      }
                  } else {
                      bytesSentAndReceived = answer.ipBytes.get(publicIp);
                  }
  
                  if (bytesSentAndReceived == null) {
                      logger.debug("Didn't get an external network usage answer for public IP " + publicIp);
                  } else {
                      newCurrentBytesSent += bytesSentAndReceived[0];
                      newCurrentBytesReceived += bytesSentAndReceived[1];
                  }
              } else {
                  URI broadcastURI = network.getBroadcastUri();
                  if (broadcastURI == null) {
                      logger.debug("Not updating stats for guest network {} because the network is not implemented.", network);
                      return true;
                  } else {
                      long vlanTag = Integer.parseInt(BroadcastDomainType.getValue(broadcastURI));
                      long[] bytesSentAndReceived = answer.guestVlanBytes.get(String.valueOf(vlanTag));
  
                      if (bytesSentAndReceived == null) {
                          logger.warn("Didn't get an external network usage answer for guest VLAN " + vlanTag);
                      } else {
                          newCurrentBytesSent += bytesSentAndReceived[0];
                          newCurrentBytesReceived += bytesSentAndReceived[1];
                      }
                  }
              }
  
              UserStatisticsVO userStats;
              try {
                  userStats = _userStatsDao.lock(accountId, zoneId, networkId, publicIp, hostId, host.getType().toString());
              } catch (Exception e) {
                  logger.warn("Unable to find user stats entry for " + statsEntryIdentifier);
                  return false;
              }
  
              if (updateBytes(userStats, newCurrentBytesSent, newCurrentBytesReceived)) {
                  logger.debug("Successfully updated stats for " + statsEntryIdentifier);
                  return true;
              } else {
                  logger.debug("Failed to update stats for " + statsEntryIdentifier);
                  return false;
              }
          }
  
          private boolean createOrUpdateStatsEntry(boolean create, long accountId, long zoneId, long networkId, String publicIp, long hostId,
              ExternalNetworkResourceUsageAnswer answer, boolean inline) {
              if (create) {
                  return createStatsEntry(accountId, zoneId, networkId, publicIp, hostId);
              } else {
                  return updateStatsEntry(accountId, zoneId, networkId, publicIp, hostId, answer, inline);
              }
          }
  
          /*
           * Creates/updates all necessary stats entries for an account and zone.
           * Stats entries are created for source NAT IP addresses, static NAT rules, port forwarding rules, and load
           * balancing rules
           */
          private boolean manageStatsEntries(final boolean create, final long accountId, final long zoneId, final Network network, final HostVO externalFirewall,
              final ExternalNetworkResourceUsageAnswer firewallAnswer, final HostVO externalLoadBalancer, final ExternalNetworkResourceUsageAnswer lbAnswer) {
              final String accountErrorMsg = "Failed to update external network stats entry. Details: account ID = " + accountId;
              try {
                  Transaction.execute(new TransactionCallbackNoReturn() {
                      @Override
                      public void doInTransactionWithoutResult(TransactionStatus status) {
                          String networkErrorMsg = accountErrorMsg + ", network ID = " + network.getId();
  
                          boolean sharedSourceNat = false;
                          Map<Network.Capability, String> sourceNatCapabilities = _networkModel.getNetworkServiceCapabilities(network.getId(), Network.Service.SourceNat);
                          if (sourceNatCapabilities != null) {
                              String supportedSourceNatTypes = sourceNatCapabilities.get(Network.Capability.SupportedSourceNatTypes).toLowerCase();
                              if (supportedSourceNatTypes.contains("zone")) {
                                  sharedSourceNat = true;
                              }
                          }
  
                          if (externalFirewall != null && firewallAnswer != null) {
                              if (!sharedSourceNat) {
                                  // Manage the entry for this network's source NAT IP address
                                  List<IPAddressVO> sourceNatIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), true);
                                  if (sourceNatIps.size() == 1) {
                                      String publicIp = sourceNatIps.get(0).getAddress().addr();
                                      if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalFirewall.getId(), firewallAnswer, false)) {
                                          throw new CloudRuntimeException(networkErrorMsg + ", source NAT IP = " + publicIp);
                                      }
                                  }
  
                                  // Manage one entry for each static NAT rule in this network
                                  List<IPAddressVO> staticNatIps = _ipAddressDao.listStaticNatPublicIps(network.getId());
                                  for (IPAddressVO staticNatIp : staticNatIps) {
                                      String publicIp = staticNatIp.getAddress().addr();
                                      if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalFirewall.getId(), firewallAnswer, false)) {
                                          throw new CloudRuntimeException(networkErrorMsg + ", static NAT rule public IP = " + publicIp);
                                      }
                                  }
  
                                  // Manage one entry for each port forwarding rule in this network
                                  List<PortForwardingRuleVO> portForwardingRules = _portForwardingRulesDao.listByNetwork(network.getId());
                                  for (PortForwardingRuleVO portForwardingRule : portForwardingRules) {
                                      String publicIp = _networkModel.getIp(portForwardingRule.getSourceIpAddressId()).getAddress().addr();
                                      if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalFirewall.getId(), firewallAnswer, false)) {
                                          throw new CloudRuntimeException(networkErrorMsg + ", port forwarding rule public IP = " + publicIp);
                                      }
                                  }
                              } else {
                                  // Manage the account-wide entry for the external firewall
                                  if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), null, externalFirewall.getId(), firewallAnswer, false)) {
                                      throw new CloudRuntimeException(networkErrorMsg);
                                  }
                              }
                          }
  
                          // If an external load balancer is added, manage one entry for each load balancing rule in this network
                          if (externalLoadBalancer != null && lbAnswer != null) {
                              boolean inline = _networkModel.isNetworkInlineMode(network);
                              List<LoadBalancerVO> loadBalancers = _loadBalancerDao.listByNetworkIdAndScheme(network.getId(), Scheme.Public);
                              for (LoadBalancerVO loadBalancer : loadBalancers) {
                                  String publicIp = _networkModel.getIp(loadBalancer.getSourceIpAddressId()).getAddress().addr();
                                  if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalLoadBalancer.getId(), lbAnswer, inline)) {
                                      throw new CloudRuntimeException(networkErrorMsg + ", load balancing rule public IP = " + publicIp);
                                  }
                              }
                          }
                      }
                  });
                  return true;
              } catch (Exception e) {
                  logger.warn("Exception: ", e);
                  return false;
              }
          }
      }
  }
  