/**
* NOTE: This copyright does *not* cover user programs that use HQ
* 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) [2009-2011], VMware, Inc.
* This file is part of HQ.
*
* HQ 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.appdef.server.session;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.agent.server.session.AgentSynchronizer;
import org.hyperic.hq.appdef.shared.AgentPluginUpdater;
import org.hyperic.hq.product.Plugin;
import org.hyperic.hq.product.shared.PluginManager;
import org.hyperic.hq.zevents.Zevent;
import org.hyperic.hq.zevents.ZeventListener;
import org.hyperic.hq.zevents.ZeventManager;
import org.hyperic.hq.zevents.ZeventPayload;
import org.hyperic.hq.zevents.ZeventSourceId;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("agentPluginUpdater")
@Transactional
public class AgentPluginUpdaterImpl
implements AgentPluginUpdater, ApplicationContextAware {
private static final Log log = LogFactory.getLog(AgentPluginUpdaterImpl.class);
private AgentSynchronizer agentSynchronizer;
private PluginManager pluginManager;
private ApplicationContext ctx;
private ZeventManager zeventManager;
@Autowired
public AgentPluginUpdaterImpl(AgentSynchronizer agentSynchronizer,
PluginManager pluginManager,
ZeventManager zeventManager) {
this.agentSynchronizer = agentSynchronizer;
this.pluginManager = pluginManager;
this.zeventManager = zeventManager;
}
@PostConstruct
public void postConstruct() {
zeventManager.addBufferedListener(PluginStatusUpdatedZevent.class,
new ZeventListener<PluginStatusUpdatedZevent>() {
public void processEvents(List<PluginStatusUpdatedZevent> events) {
for (final PluginStatusUpdatedZevent event : events) {
queueJobs(event.restartAgents(), event.getUpdateMap(), event.getRemoveMap());
}
}
}
);
}
private void queueJobs(boolean restartAgents,
Map<Integer, Collection<Plugin>> updateMap,
Map<Integer, Collection<String>> removeMap) {
final Set<Integer> agentIds = new HashSet<Integer>();
agentIds.addAll(updateMap.keySet());
agentIds.addAll(removeMap.keySet());
final boolean debug = log.isDebugEnabled();
for (final Integer agentId : agentIds) {
Collection<Plugin> plugins = updateMap.get(agentId);
Collection<String> toRemove = removeMap.get(agentId);
if (plugins == null) {
plugins = Collections.emptyList();
}
if (toRemove == null) {
toRemove = Collections.emptyList();
}
if (debug) {
log.debug("queue plugin transfer for agentId=" + agentId +
" plugins=" + plugins);
log.debug("queue plugin remove for agentId=" + agentId +
" filenames=" + toRemove);
}
removeDuplicates(plugins, toRemove);
final PluginSyncJob job = ctx.getBean(PluginSyncJob.class);
job.setAgentId(agentId);
job.setPlugins(plugins);
job.setToRemove(toRemove);
job.restartAgent(restartAgents);
agentSynchronizer.addAgentJob(job);
}
}
@Transactional(readOnly=false)
public void queuePluginTransfer(Map<Integer, Collection<Plugin>> updates,
Map<Integer, Collection<String>> removes,
boolean restartAgents) {
if (isDisabled()) {
return;
}
@SuppressWarnings("unchecked")
final Map<Integer, Collection<Plugin>> updateMap =
(updates == null) ? Collections.EMPTY_MAP : updates;
@SuppressWarnings("unchecked")
final Map<Integer, Collection<String>> removeMap =
(removes == null) ? Collections.EMPTY_MAP : removes;
if (restartAgents) {
pluginManager.updateAgentPluginSyncStatus(
AgentPluginStatusEnum.SYNC_IN_PROGRESS, updateMap, removeMap);
} else {
pluginManager.updateAgentPluginSyncStatus(
AgentPluginStatusEnum.SYNC_FAILURE, updateMap, removeMap);
}
// want jobs to get added to the agentSynchronizer after commit to ensure that we don't
// have agents restarting and updating status before the status is updated as a result
// of the updateAgentPluginSyncStatus() call. If the queuing was not called this way we
// could have agents sitting in the "IN-PROGRESS" state indefinitely because they were
// updated out of order
zeventManager.enqueueEventAfterCommit(
new PluginStatusUpdatedZevent(restartAgents, updateMap, removeMap));
}
private void removeDuplicates(Collection<Plugin> plugins, Collection<String> toRemove) {
final Set<String> filenames = new HashSet<String>();
for (final Plugin plugin : plugins) {
filenames.add(plugin.getPath());
}
final boolean debug = log.isDebugEnabled();
for (final Iterator<String> it=toRemove.iterator(); it.hasNext(); ) {
final String file = it.next();
if (filenames.contains(file)) {
if (debug) log.debug("will not remove " + file + " since it is being transferred");
it.remove();
}
}
}
public void queuePluginRemoval(Map<Integer, Collection<String>> agentToFileNames) {
if (agentToFileNames == null || agentToFileNames.isEmpty()) {
return;
}
pluginManager.updateAgentPluginSyncStatus(
AgentPluginStatusEnum.SYNC_IN_PROGRESS, null, agentToFileNames);
for (final Entry<Integer, Collection<String>> entry : agentToFileNames.entrySet()) {
final Integer agentId = entry.getKey();
final Collection<String> pluginFileNames = entry.getValue();
final AgentRemovePluginJob job = ctx.getBean(AgentRemovePluginJob.class);
job.setAgentId(agentId);
job.setPluginFileNames(pluginFileNames);
agentSynchronizer.addAgentJob(job);
}
}
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
}
private boolean isDisabled() {
return !pluginManager.isPluginSyncEnabled();
}
private class PluginStatusUpdatedZevent extends Zevent {
private Map<Integer, Collection<Plugin>> updateMap;
private Map<Integer, Collection<String>> removeMap;
private boolean restartAgents;
@SuppressWarnings("serial")
private PluginStatusUpdatedZevent(boolean restartAgents,
Map<Integer, Collection<Plugin>> updateMap,
Map<Integer, Collection<String>> removeMap) {
super(new ZeventSourceId() {}, new ZeventPayload() {});
this.updateMap = updateMap;
this.removeMap = removeMap;
this.restartAgents = restartAgents;
}
public boolean restartAgents() {
return restartAgents;
}
private Map<Integer, Collection<Plugin>> getUpdateMap() {
return updateMap;
}
private Map<Integer, Collection<String>> getRemoveMap() {
return removeMap;
}
}
}