// Copyright 2012 Citrix Systems, Inc. Licensed under the // Apache License, Version 2.0 (the "License"); you may not use this // file except in compliance with the License. Citrix Systems, Inc. // reserves all rights not expressly granted by 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. // // Automatically generated by addcopyright.py at 04/03/2012 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.ejb.Local; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; 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.configuration.dao.ConfigurationDao; 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.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.dao.ExternalFirewallDeviceDao; import com.cloud.network.dao.ExternalLoadBalancerDeviceDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.InlineLoadBalancerNicMapDao; import com.cloud.network.dao.LoadBalancerDao; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkExternalFirewallDao; import com.cloud.network.dao.NetworkExternalLoadBalancerDao; import com.cloud.network.dao.NetworkServiceMapDao; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; import com.cloud.network.rules.dao.PortForwardingRulesDao; 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.Inject; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.ExecutionException; import com.cloud.vm.DomainRouterVO; import com.cloud.vm.NicVO; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; @Local(value = { ExternalLoadBalancerUsageManager.class }) public class ExternalLoadBalancerUsageManagerImpl implements ExternalLoadBalancerUsageManager { String _name; @Inject NetworkExternalLoadBalancerDao _networkExternalLBDao; @Inject ExternalLoadBalancerDeviceDao _externalLoadBalancerDeviceDao; @Inject HostDao _hostDao; @Inject DataCenterDao _dcDao; @Inject NetworkManager _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 HostDetailsDao _hostDetailDao; @Inject NetworkExternalLoadBalancerDao _networkLBDao; @Inject NetworkServiceMapDao _ntwkSrvcProviderDao; @Inject NetworkExternalFirewallDao _networkExternalFirewallDao; @Inject ExternalFirewallDeviceDao _externalFirewallDeviceDao; @Inject protected HostPodDao _podDao = null; ScheduledExecutorService _executor; private int _externalNetworkStatsInterval; private static final org.apache.log4j.Logger s_logger = Logger.getLogger(ExternalLoadBalancerUsageManagerImpl.class); @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 ExternalLoadBalancerDeviceNetworkUsageTask(), _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 boolean externalLoadBalancerIsInline(HostVO externalLoadBalancer) { DetailVO detail = _hostDetailDao.findDetail(externalLoadBalancer.getId(), "inline"); return (detail != null && detail.getValue().equals("true")); } @Override public void updateExternalLoadBalancerNetworkUsageStats(long loadBalancerRuleId){ LoadBalancerVO lb = _loadBalancerDao.findById(loadBalancerRuleId); if(lb == null){ if(s_logger.isDebugEnabled()){ s_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(s_logger.isDebugEnabled()){ s_logger.debug("Cannot update usage stats, Network is not found"); } return; } ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network); if (lbDeviceVO == null) { if(s_logger.isDebugEnabled()){ s_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 = "Unable to get external load balancer stats for network" + networkId + " due to: " + details + "."; s_logger.error(msg); return; } } long accountId = lb.getAccountId(); AccountVO account = _accountDao.findById(accountId); if (account == null) { s_logger.debug("Skipping stats update for external LB for account with ID " + accountId); return; } String publicIp = _networkMgr.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; if (externalLoadBalancer.getType().equals(Host.Type.ExternalLoadBalancer) && externalLoadBalancerIsInline(externalLoadBalancer)) { // 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.getIp4Address(); bytesSentAndReceived = lbAnswer.ipBytes.get(loadBalancingIpAddress); if (bytesSentAndReceived != null) { bytesSentAndReceived[0] = 0; } } } else { bytesSentAndReceived = lbAnswer.ipBytes.get(publicIp); } if (bytesSentAndReceived == null) { s_logger.debug("Didn't get an external network usage answer for public IP " + publicIp); } else { newCurrentBytesSent += bytesSentAndReceived[0]; newCurrentBytesReceived += bytesSentAndReceived[1]; } UserStatisticsVO userStats; final Transaction txn = Transaction.currentTxn(); try { txn.start(); 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) { s_logger.warn(warning + "Stored bytes sent: " + oldCurrentBytesSent + ", new bytes sent: " + newCurrentBytesSent + "."); userStats.setNetBytesSent(oldNetBytesSent + oldCurrentBytesSent); } userStats.setCurrentBytesReceived(newCurrentBytesReceived); if (oldCurrentBytesReceived > newCurrentBytesReceived) { s_logger.warn(warning + "Stored bytes received: " + oldCurrentBytesReceived + ", new bytes received: " + newCurrentBytesReceived + "."); userStats.setNetBytesReceived(oldNetBytesReceived + oldCurrentBytesReceived); } if (_userStatsDao.update(userStats.getId(), userStats)) { s_logger.debug("Successfully updated stats for " + statsEntryIdentifier); } else { s_logger.debug("Failed to update stats for " + statsEntryIdentifier); } }else { s_logger.warn("Unable to find user stats entry for " + statsEntryIdentifier); } txn.commit(); }catch (final Exception e) { txn.rollback(); throw new CloudRuntimeException("Problem getting stats after reboot/stop ", e); } } } protected class ExternalLoadBalancerDeviceNetworkUsageTask implements Runnable { public ExternalLoadBalancerDeviceNetworkUsageTask() { } @Override public void run() { GlobalLock scanLock = GlobalLock.getInternLock("ExternalLoadBalancerUsageManagerImpl"); try { if (scanLock.lock(20)) { try { runExternalLoadBalancerNetworkUsageTask(); } finally { scanLock.unlock(); } } } catch (Exception e) { s_logger.warn("Problems while getting external load balancer device usage", e); } finally { scanLock.releaseRef(); } } private void runExternalLoadBalancerNetworkUsageTask() { s_logger.debug("External load balancer 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>(); List<Long> accountsProcessed = new ArrayList<Long>(); for (DomainRouterVO domainRouter : domainRoutersInZone) { long accountId = domainRouter.getAccountId(); if (accountsProcessed.contains(new Long(accountId))) { if (s_logger.isTraceEnabled()) { s_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.listBy(accountId, zoneId, Network.GuestType.Isolated); if (networksForAccount == null) { continue; } for (NetworkVO network : networksForAccount) { if (!_networkMgr.networkIsConfiguredForExternalNetworking(zoneId, network.getId())) { s_logger.debug("Network " + network.getId() + " is not configured for external networking, so skipping usage check."); continue; } ExternalLoadBalancerDeviceVO lbDeviceVO = getExternalLoadBalancerForNetwork(network); if (lbDeviceVO == null) { continue; } // Get network stats from the external load balancer ExternalNetworkResourceUsageAnswer lbAnswer = null; HostVO externalLoadBalancer = _hostDao.findById(lbDeviceVO.getHostId()); if (externalLoadBalancer != null) { Long lbDeviceId = new Long(externalLoadBalancer.getId()); if (!lbDeviceUsageAnswerMap.containsKey(lbDeviceId)) { 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 = "Unable to get external load balancer stats for " + zone.getName() + " due to: " + details + "."; s_logger.error(msg); continue; } lbDeviceUsageAnswerMap.put(lbDeviceId, lbAnswer); } else { if (s_logger.isTraceEnabled()) { s_logger.trace("Reusing usage Answer for device id " + lbDeviceId + "for Network " + network.getId()); } lbAnswer = lbDeviceUsageAnswerMap.get(lbDeviceId); } } AccountVO account = _accountDao.findById(accountId); if (account == null) { s_logger.debug("Skipping stats update for account with ID " + accountId); continue; } if (!manageStatsEntries(true, accountId, zoneId, network, externalLoadBalancer, lbAnswer)) { continue; } manageStatsEntries(false, accountId, zoneId, network, 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) { s_logger.warn(warning + "Stored bytes sent: " + oldCurrentBytesSent + ", new bytes sent: " + newCurrentBytesSent + "."); userStats.setNetBytesSent(oldNetBytesSent + oldCurrentBytesSent); } userStats.setCurrentBytesReceived(newCurrentBytesReceived); if (oldCurrentBytesReceived > newCurrentBytesReceived) { s_logger.warn(warning + "Stored bytes received: " + oldCurrentBytesReceived + ", new bytes received: " + 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) { 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) && externalLoadBalancerIsInline(host)) { // 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.getIp4Address(); bytesSentAndReceived = answer.ipBytes.get(loadBalancingIpAddress); if (bytesSentAndReceived != null) { bytesSentAndReceived[0] = 0; } } } else { bytesSentAndReceived = answer.ipBytes.get(publicIp); } if (bytesSentAndReceived == null) { s_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) { s_logger.debug("Not updating stats for guest network with ID " + network.getId() + " because the network is not implemented."); return true; } else { long vlanTag = Integer.parseInt(broadcastURI.getHost()); long[] bytesSentAndReceived = answer.guestVlanBytes.get(String.valueOf(vlanTag)); if (bytesSentAndReceived == null) { s_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) { s_logger.warn("Unable to find user stats entry for " + statsEntryIdentifier); return false; } if (updateBytes(userStats, newCurrentBytesSent, newCurrentBytesReceived)) { s_logger.debug("Successfully updated stats for " + statsEntryIdentifier); return true; } else { s_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) { if (create) { return createStatsEntry(accountId, zoneId, networkId, publicIp, hostId); } else { return updateStatsEntry(accountId, zoneId, networkId, publicIp, hostId, answer); } } /* * 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(boolean create, long accountId, long zoneId, Network network, HostVO externalLoadBalancer, ExternalNetworkResourceUsageAnswer lbAnswer) { String accountErrorMsg = "Failed to update external network stats entry. Details: account ID = " + accountId; Transaction txn = Transaction.open(Transaction.CLOUD_DB); try { txn.start(); String networkErrorMsg = accountErrorMsg + ", network ID = " + network.getId(); // If an external load balancer is added, manage one entry for each load balancing rule in this network if (externalLoadBalancer != null && lbAnswer != null) { List<LoadBalancerVO> loadBalancers = _loadBalancerDao.listByNetworkId(network.getId()); for (LoadBalancerVO loadBalancer : loadBalancers) { String publicIp = _networkMgr.getIp(loadBalancer.getSourceIpAddressId()).getAddress().addr(); if (!createOrUpdateStatsEntry(create, accountId, zoneId, network.getId(), publicIp, externalLoadBalancer.getId(), lbAnswer)) { throw new ExecutionException(networkErrorMsg + ", load balancing rule public IP = " + publicIp); } } } return txn.commit(); } catch (Exception e) { s_logger.warn("Exception: ", e); txn.rollback(); return false; } finally { txn.close(); } } } }