/*
* NOTE: This copyright does *not* cover user programs that use Hyperic
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004-2013], VMware, Inc.
* This file is part of Hyperic.
*
* Hyperic is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. This program is distributed
* in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.measurement.server.session;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* An object that manages a que of Platform IDs for platforms for which fallback availability check needs to be done.
* Every X seconds, a FallbackChecker polls N platform IDs, checks for their availability, and re-adds them to the Que
*
* <BR><B><U>Que flow:</U></B>
* <BR><B>addToQue</B> - Platform ID is added to the platformsRecheckQue, if it's not yet there.
* <BR><B>poll</B> - PlatformID moves from platformsRecheckQue to platformsRecheckInProgress member.
* <BR><B>beforeDataUpdate</B> - before availability data is about to be stored in the DB/Cache, the que is updated:
* <BR> - if this is an update from the server:
* <BR> If PlatformID exists in platformsPendingQueRemoval - it is removed from all the members/
* <BR> Otherwise: PlatformID is moved back from platformsRecheckInProgress into platformsRecheckQue
* to be rechecked after the next interval.
* <BR>- if this is an update from the agent:
* <BR> if currently PlatformID is not processed (it is not in platformsRecheckInProgress) - it is removed from all Que members.
* <BR> Otherwise: PlatformID is added to platformsPendingQueRemoval, and will be cleaned once the recheck process ends.
*
* @author amalia
*
*/
public class AvailabilityFallbackCheckQue {
// TODO: following code review:
// minimize checks here since these are synchronized updates of availability data.
// HowTo? Remove the need for platformsRecheckInProgress: Add LastTimeUpdate to the ResourceDataPoint. This is the lastTimeStamp that we know
// of when starting to calculate availability for a Platform. before availMgr updates data,
// where it checks for timestamps mismatches, it can check if the the LastTimeUpdate is before the actual last timestamp in the cache, and if so -
// remove the platform from the que, and not update it.
private final Log log = LogFactory.getLog(AvailabilityFallbackCheckQue.class);
// a que holding the platforms pending recheck
private final ConcurrentLinkedQueue<ResourceDataPoint> platformsRecheckQue;
// Map:PlatformID->latest availability ResourceDataPoint>, for quick-access of platformsRecheckQue items.
private final Map<Integer, ResourceDataPoint> currentPlatformsInQue;
// Set of Platform IDs, for platforms whose status is currently checked by AvailabilityFallbackChecker.
private final Set<Integer> platformsRecheckInProgress;
// Set of Platform IDs, for platforms currently rechecked, which agent information was sent.
private final Set<Integer> platformsPendingQueRemoval;
// Map: MeasurementID->PlatformID for quick-access
private final Map<Integer,Integer> measurementIdToPlatformId;
// -----------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------
/**
* Default CTor
*/
public AvailabilityFallbackCheckQue() {
this.platformsRecheckQue = new ConcurrentLinkedQueue<ResourceDataPoint>();
this.currentPlatformsInQue = new HashMap<Integer, ResourceDataPoint>();
this.platformsRecheckInProgress = new HashSet<Integer>();
this.platformsPendingQueRemoval = new HashSet<Integer>();
this.measurementIdToPlatformId = new HashMap<Integer,Integer>();
}
public void clearQue() {
log.info("Clearing availability check queues.");
this.platformsRecheckQue.clear();
this.currentPlatformsInQue.clear();
this.platformsRecheckInProgress.clear();
this.platformsPendingQueRemoval.clear();
this.measurementIdToPlatformId.clear();
}
/**
* @return the number of Platforms pending availability status check
*/
public synchronized int getSize(){
return this.platformsRecheckQue.size();
}
// -----------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------
/**
* Add a single platform to the rechecks que.
* @param platformId - the platform to be checked.
* @param dataPoint - the latest status associated with the platform
* @return true, if added. false, if the platform is already in the que and will not be re-added.
*/
public synchronized boolean addToQue(Integer platformId, ResourceDataPoint dataPoint) {
if (log.isDebugEnabled()) {
log.debug("addToQue: start resourcePlatformId=" + platformId+ ", curQueSize: " + getSize());
}
if (this.currentPlatformsInQue.containsKey(platformId)) {
// fix for jira issue HQ-4325 - keep the existing platform down data
// point timestamp updated to the last availability check time
ResourceDataPoint existingDataPoint = this.currentPlatformsInQue.get(platformId);
existingDataPoint.getMetricValue().setTimestamp(dataPoint.getTimestamp());
// in case there was an agent update in the middle, we do not want to remove it from the recheck que.
//this.platformsPendingQueRemoval.remove(platformId);
return false;
}
this.platformsRecheckQue.add(dataPoint);
this.currentPlatformsInQue.put(platformId, dataPoint);
this.measurementIdToPlatformId.put(dataPoint.getMeasurementId(), platformId);
return true;
}
/**
* Adds a collection of platforms to the rechecks que. Platforms that already exist in the que will not be re-added.
* @param platformsToAdd - map:PlatformID->latest status associated with the platform
* @return the number of platforms actually added.
*/
public synchronized int addToQue(Map<Integer, ResourceDataPoint> platformsToAdd) {
int res = 0;
for (Integer platformId : platformsToAdd.keySet()) {
boolean added = addToQue(platformId, platformsToAdd.get(platformId));
res += added ? 1 : 0;
}
if (log.isDebugEnabled()) {
log.debug("addToQue: added "+res);
}
return res;
}
/**
* Poll a platform to check availability for,
* @return the next ResourceDataPoint in the que. The ResourceDataPoint is the latest status of the checked platform. Return null if the que is empty.
*/
public synchronized ResourceDataPoint poll() {
if (log.isDebugEnabled()) {
log.debug("poll: start, que size: " + getSize());
}
ResourceDataPoint res = this.platformsRecheckQue.poll();
if (res != null) {
Integer platformId = res.getResource().getId();
this.platformsRecheckInProgress.add(platformId);
}
return res;
}
/**
* A method to be called before storing availability status updates.
* <BR> This method updates the que and filters the given dataPoints list, so that agent updates will always override server calculations.
* <BR> In case the update is from the agent:
* <BR> - the returned list is identical to the input list.
* <BR> - all platforms that are about to be updated are removed from the que (or added to the PendingRemoval list, if check is in progress).
* <BR> In case the update is from the server:
* <BR> - if the platform are in the PendingRemoval list - they are removed completely from the Que, and not inserted into the result.
* <BR> - Otherwise - they are added into the result, and platforms are re-inserted into the recheckQue.
* <BR> <B> Notes: </B>
* <BR> - Case 1: agent updated platform status which recheck was done. In this case, the platform's descendants will not be added into the dataPoints
* input since the checker will not add descendants with updated availability status. In case it will: the descendant status will remain as "UNKNOWN" until
* the next agent status update (half-an-interval or so).
* <BR> - Case 2: Over-checking: A platform is checked and then re-inserted to the que.
* <BR> In case of threads working on the same que, we may get over-checking, instead of checking every 2 minutes or so.
* <BR> If we start working with several threads we need to use 2 queues intermittently - all threads poll from Que1 until the que is empty,
* every checked platform is inserted into Que2. In the next checks-round - poll from Que2 and insert into Que1.
* @param dataPoints - a collection of availability datapoints about to be added.
* @param isUpdateFromServer - true, if the server fallback checks asked for the updated, false if the agent updated with the status.
* @return a filtered collection of datapoints, the ones that should be updated in the DB/cache
*/
public synchronized Collection<DataPoint> beforeDataUpdate(Collection<DataPoint> dataPoints,
boolean isUpdateFromServer) {
Collection<DataPoint> res = new ArrayList<DataPoint>();
Collection<DataPoint> removed = new ArrayList<DataPoint>();
final boolean debug = log.isDebugEnabled();
for (DataPoint dataPoint : dataPoints) {
Integer measurementId = dataPoint.getMeasurementId();
Integer resourceId = this.measurementIdToPlatformId.get(measurementId);
if (resourceId == null) {
// we do not know this resource as a platformId. one of:
// 1. this is not a platform - update it.
// 2. this is a platform that is not in the recheck que - update it.
res.add(dataPoint);
continue;
}
Integer platformId = resourceId;
if (isUpdateFromServer) {
this.platformsRecheckInProgress.remove(platformId);
if (this.platformsPendingQueRemoval.contains(platformId)) {
// this should not be updated from the server.
this.platformsPendingQueRemoval.remove(platformId);
ResourceDataPoint platformDataPoint = this.currentPlatformsInQue.get(platformId);
this.platformsRecheckQue.remove(platformDataPoint);
this.measurementIdToPlatformId.remove(measurementId);
this.currentPlatformsInQue.remove(platformId);
if (debug) {
removed.add(dataPoint);
}
} else {
res.add(dataPoint);
ResourceDataPoint queDataPoint = this.currentPlatformsInQue.get(platformId);
ResourceDataPoint pointToAddToQue = new ResourceDataPoint(queDataPoint.getResource(), dataPoint);
this.currentPlatformsInQue.remove(platformId);
addToQue(platformId, pointToAddToQue);
}
} else {
// update from agent
res.add(dataPoint);
removeFromQueBeforeUpdateFromAgent(platformId);
}
}
if (isUpdateFromServer) {
if (debug) {
log.debug("beforeDataUpdate from Server: updating: " + res.size() + " out of " + dataPoints.size() +
", removed=" + removed);
}
}
return res;
}
// -----------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------
// return platform Id
private synchronized Integer removeFromCurrentPlatformsInQue(ResourceDataPoint dp) {
Integer platformId = null;
for (Entry<Integer, ResourceDataPoint> entry : currentPlatformsInQue.entrySet()) {
if (dp.equals(entry.getValue())) {
platformId = entry.getKey();
break;
}
}
if (platformId != null) {
currentPlatformsInQue.remove(platformId);
}
return platformId;
}
// return measurement Id
private synchronized Integer removeFromMeasIdToPlatId(Integer platformId) {
Integer measId = null;
for (Entry<Integer, Integer> entry : measurementIdToPlatformId.entrySet()) {
if (platformId.equals(entry.getValue())) {
measId = entry.getKey();
break;
}
}
if (measId != null) {
measurementIdToPlatformId.remove(measId);
}
return measId;
}
public synchronized int cleanQueFromNonExistant() {
int res = 0;
Collection<ResourceDataPoint> pointsToDel = new ArrayList<ResourceDataPoint>();
for (ResourceDataPoint dp : this.platformsRecheckQue) {
if ((dp.getResource() == null) || (dp.getResource().isInAsyncDeleteState()) ) {
res++;
pointsToDel.add(dp);
Integer platformId = removeFromCurrentPlatformsInQue(dp);
if (platformId != null) {
removeFromMeasIdToPlatId(platformId);
}
}
}
platformsRecheckQue.removeAll(pointsToDel);
if ((res != 0) && log.isDebugEnabled()) {
log.debug("cleanQueFromNonExistant: removing " + res + " platforms.");
}
return res;
}
public synchronized int removeFromQue(Collection<Integer> platformResourceIds) {
int res = 0;
final boolean debug = log.isDebugEnabled();
for (Integer platformId : platformResourceIds) {
if (this.platformsRecheckInProgress.contains(platformId)) {
if (debug) {
log.debug("0 adding to pending queue platformId=" + platformId+ ", curQueSize: " + getSize());
}
this.platformsPendingQueRemoval.add(platformId);
continue;
}
ResourceDataPoint platformDataPoint = this.currentPlatformsInQue.get(platformId);
if (platformDataPoint == null) {
// this point is not in the que.
continue;
}
//else
this.platformsRecheckQue.remove(platformDataPoint);
this.currentPlatformsInQue.remove(platformId);
Integer measId = platformDataPoint.getMeasurementId();
this.measurementIdToPlatformId.remove(measId);
res++;
}
return res;
}
private synchronized boolean removeFromQueBeforeUpdateFromAgent(Integer platformId) {
if (this.platformsRecheckInProgress.contains(platformId)) {
if (log.isDebugEnabled()) {
log.debug("adding to pending queue removal platformId=" + platformId+ ", curQueSize: " + getSize());
}
this.platformsPendingQueRemoval.add(platformId);
return true;
}
ResourceDataPoint platformDataPoint = this.currentPlatformsInQue.get(platformId);
if (platformDataPoint == null) {
// this point is not in the que.
return false;
}
//else
this.platformsRecheckQue.remove(platformDataPoint);
this.currentPlatformsInQue.remove(platformId);
Integer measId = platformDataPoint.getMeasurementId();
this.measurementIdToPlatformId.remove(measId);
return true;
}
}