/*
* 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.notifications;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.context.Bootstrap;
import org.hyperic.hq.notifications.filtering.MetricDestinationEvaluator;
import org.hyperic.hq.notifications.filtering.ResourceDestinationEvaluator;
import org.hyperic.hq.notifications.model.BaseNotification;
import org.hyperic.hq.notifications.model.InternalResourceDetailsType;
import org.hyperic.hq.stats.ConcurrentStatsCollector;
import org.hyperic.util.Transformer;
import org.hyperic.util.stats.StatCollector;
import org.hyperic.util.stats.StatUnreachableException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
@Component
public class EndpointQueue {
private final Log log = LogFactory.getLog(EndpointQueue.class);
// XXX should make this configurable in some way
private static final int QUEUE_LIMIT = 100000;
private static final long TASK_INTERVAL = 30000;
private static final String NOTIFICATIONS_PUBLISHED_TO_ENDPOINT = ConcurrentStatsCollector.NOTIFICATIONS_PUBLISHED_TO_ENDPOINT;
private static final String NOTIFICATIONS_PUBLISHED_TO_ENDPOINT_TIME = ConcurrentStatsCollector.NOTIFICATIONS_PUBLISHED_TO_ENDPOINT_TIME;
private static final String NOTIFICATION_TOTAL_QUEUE_SIZE = ConcurrentStatsCollector.NOTIFICATION_TOTAL_QUEUE_SIZE;
// XXX should make this configurable in some way
private static final int BATCH_SIZE = 25000;
@Autowired
private ThreadPoolTaskScheduler notificationExecutor;
@Autowired
private ConcurrentStatsCollector concurrentStatsCollector;
protected final static long EXPIRATION_DURATION = 10*60*1000;
MetricDestinationEvaluator metricEvaluator;
ResourceDestinationEvaluator resourceEvaluator;
private final Map<String, AccumulatedRegistrationData> registrationData = new HashMap<String, AccumulatedRegistrationData>();
private final AtomicInteger numConsumers = new AtomicInteger(0);
public void register(NotificationEndpoint endpoint, Transformer<InternalNotificationReport, String> transformer) {
register(endpoint,null,transformer);
}
public int getNumConsumers() {
return numConsumers.get();
}
@PostConstruct
public void init() {
metricEvaluator = (MetricDestinationEvaluator) Bootstrap.getBean("metricDestinationEvaluator");
resourceEvaluator = (ResourceDestinationEvaluator) Bootstrap.getBean("resourceDestinationEvaluator");
concurrentStatsCollector.register(NOTIFICATIONS_PUBLISHED_TO_ENDPOINT);
concurrentStatsCollector.register(NOTIFICATIONS_PUBLISHED_TO_ENDPOINT_TIME);
concurrentStatsCollector.register(new StatCollector() {
public long getVal() throws StatUnreachableException {
synchronized (registrationData) {
long rtn = 0;
for (final AccumulatedRegistrationData data : registrationData.values()) {
rtn += data.getAccumulatedNotificationsQueue().size();
}
return rtn;
}
}
public String getId() {
return NOTIFICATION_TOTAL_QUEUE_SIZE;
}
});
}
public void register(NotificationEndpoint endpoint, InternalResourceDetailsType resourceDetailsType,
Transformer<InternalNotificationReport, String> transformer) {
final boolean debug = log.isDebugEnabled();
final AccumulatedRegistrationData data =
new AccumulatedRegistrationData(endpoint, QUEUE_LIMIT, resourceDetailsType, registrationData);
synchronized (registrationData) {
if (registrationData.containsKey(endpoint.getRegistrationId())) {
if (debug) log.debug("can not register endpoint=" + endpoint + " twice");
return;
}
final String regId = endpoint.getRegistrationId();
registrationData.put(regId, data);
numConsumers.incrementAndGet();
schedule(endpoint, data, transformer);
}
if (log.isDebugEnabled()) {
log.debug("new notification registration=" + endpoint);
}
}
private void schedule(final NotificationEndpoint endpoint, final AccumulatedRegistrationData data,
final Transformer<InternalNotificationReport, String> transformer) {
if (!endpoint.canPublish()) {
return;
}
final Runnable task = new Runnable() {
protected long firstFailure=Long.MAX_VALUE;
protected boolean isFailedLastPostage = false;
public void run() {
int size = 0;
long totalTime = 0;
try {
final String registrationId = endpoint.getRegistrationId();
InternalNotificationReport report = null;
final long start = System.currentTimeMillis();
final Collection<InternalAndExternalNotificationReports> messages =
new ArrayList<InternalAndExternalNotificationReports>();
List<InternalNotificationReport> reports = new ArrayList<InternalNotificationReport>();
while (report == null || !report.getNotifications().isEmpty()) {
report = poll(registrationId, BATCH_SIZE);
reports.add(report);
final String toPublish = transformer.transform(report);
messages.add(new InternalAndExternalNotificationReports(report,toPublish));
size += report.getNotifications().size();
}
List<InternalNotificationReport> failedReports = new ArrayList<InternalNotificationReport>();
EndpointStatus batchPostingStatus = endpoint.publishMessagesInBatch(messages,failedReports);
checkResultStatus(batchPostingStatus, failedReports);
totalTime = System.currentTimeMillis() - start;
} catch (Throwable t) {
log.error(t, t);
} finally {
concurrentStatsCollector.addStat(size, NOTIFICATIONS_PUBLISHED_TO_ENDPOINT);
concurrentStatsCollector.addStat(totalTime, NOTIFICATIONS_PUBLISHED_TO_ENDPOINT_TIME);
}
}
private void checkResultStatus(EndpointStatus batchPostingStatus,
List<InternalNotificationReport> failedReports) {
if (!batchPostingStatus.isEmpty()) {
// retry the reports which were failed to be published
for(InternalNotificationReport failedReport:failedReports) {
@SuppressWarnings("unchecked")
List<BaseNotification> failedNotifications = (List<BaseNotification>) failedReport.getNotifications();
Map<NotificationEndpoint, Collection<BaseNotification>> map = new HashMap<NotificationEndpoint, Collection<BaseNotification>>();
map.put(endpoint, failedNotifications);
publishAsync(map);
}
data.merge(batchPostingStatus);
// if the last try was a failure, it means that problem sending notifications
// to the endpoint has happened before finishing the whole messages transmission
if (batchPostingStatus.getLast().isSuccessful()) {
this.isFailedLastPostage=false;
} else {
if (!this.isFailedLastPostage) {
this.isFailedLastPostage=true;
this.firstFailure = batchPostingStatus.getLast().getTime();
}
if (System.currentTimeMillis() - this.firstFailure >= EXPIRATION_DURATION) {
unregister(endpoint.getRegistrationId());
metricEvaluator.unregisterAll(endpoint);
resourceEvaluator.unregisterAll(endpoint);
}
}
}
}
};
final Date start = new Date(System.currentTimeMillis() + TASK_INTERVAL);
ScheduledFuture<?> schedule = notificationExecutor.scheduleWithFixedDelay(task, start, TASK_INTERVAL);
data.setSchedule(schedule);
}
public NotificationEndpoint unregister(String regID) {
AccumulatedRegistrationData data = null;
synchronized (registrationData) {
// don't delete the data, we want to be able to access the endpoint by registrationId
data = registrationData.get(regID);
if (data == null || !data.isValid()) {
if(log.isDebugEnabled()){
log.debug((data == null ? "No queue assigned" : "Queue is already invalid") + " for regId: " +
regID);
}
return null;
}
numConsumers.decrementAndGet();
data.markInvalid();
data.clear();
final ScheduledFuture<?> schedule = data.getSchedule();
if (schedule != null) {
schedule.cancel(true);
}
if (log.isDebugEnabled()) {
log.debug("Removing the queue assigned for regId: " + regID);
}
}
return data.getNotificationEndpoint();
}
public InternalNotificationReport poll(String registrationId) {
return poll(registrationId,Integer.MAX_VALUE);
}
public InternalNotificationReport poll(String registrationId, int maxSize) {
final InternalNotificationReport rtn = new InternalNotificationReport();
final List<BaseNotification> notifications = new ArrayList<BaseNotification>();
synchronized (registrationData) {
AccumulatedRegistrationData data = registrationData.get(registrationId);
if (data == null || !data.isValid()) {
return rtn;
}
data.drainTo(notifications, maxSize);
rtn.setNotifications(notifications);
rtn.setResourceDetailsType(data.getResourceContentType());
return rtn;
}
}
public <T extends BaseNotification> void publishAsync(Map<NotificationEndpoint, Collection<T>> map) {
synchronized (registrationData) {
for (final Entry<NotificationEndpoint, Collection<T>> entry : map.entrySet()) {
final NotificationEndpoint endpoint = entry.getKey();
final Collection<T> list = entry.getValue();
final AccumulatedRegistrationData data = registrationData.get(endpoint.getRegistrationId());
if (data != null) {
data.addAll(list);
}
}
}
}
public NotificationEndpoint getEndpoint(String registrationID) {
if (registrationID == null) {
return null;
}
synchronized (registrationData) {
AccumulatedRegistrationData data = registrationData.get(registrationID);
if (data != null) {
return data.getNotificationEndpoint();
}
}
return null;
}
public EndpointAndRegStatus getEndpointAndRegStatus(String registrationID) {
synchronized (registrationData) {
AccumulatedRegistrationData ard = this.registrationData.get(registrationID);
if (ard!=null) {
RegistrationStatus regStat = new RegistrationStatus();
regStat.setCreationTime(ard.getCreationTime());
regStat.setValid(ard.isValid());
EndpointStatus endpointStatus = null;
EndpointStatus ardStatus = ard.getEndpointStatus();
if (ardStatus!=null) {
endpointStatus = new EndpointStatus(ardStatus);
}
return new EndpointAndRegStatus(endpointStatus,regStat);
} else {
this.log.error("there is no AccumulatedRegistrationData for registration " + registrationID);
}
}
return null;
}
public static class EndpointAndRegStatus {
protected EndpointStatus endpointStatus;
protected RegistrationStatus regStat;
public EndpointAndRegStatus(EndpointStatus endpointStatus, RegistrationStatus regStat) {
super();
this.endpointStatus = endpointStatus;
this.regStat = regStat;
}
public EndpointStatus getEndpointStatus() {
return endpointStatus;
}
public RegistrationStatus getRegStatus() {
return regStat;
}
}
}