/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.cloud;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.alert.AlertDefinition;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.cloud.Server;
import org.rhq.core.domain.measurement.MeasurementBaseline;
import org.rhq.core.domain.resource.Agent;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.alert.AlertDefinitionManagerLocal;
import org.rhq.enterprise.server.cloud.instance.CacheConsistencyManagerBean;
import org.rhq.enterprise.server.cloud.instance.ServerManagerLocal;
import org.rhq.enterprise.server.core.AgentManagerLocal;
import org.rhq.enterprise.server.util.LookupUtil;
/**
* There are various changes that can occur in the system that make the alertscondition cache stale.
* This session bean interfaces captures those various types of changes, and sets a bit-mask status
* field on the agent managing the data that was changed. This status field is later checked by the
* {@link CacheConsistencyManagerBean} to determine what data needs to be reloaded.
*
* Unless we're debugging, let's use the status field on the {@link Agent} and {@link Server} entities
* as a simple bit field; this way the logic for setting the field simplifies to a simple boolean check
* instead of a more complex bit
*
* @author Joseph Marques
*/
/*
* All public methods are marked as requires new because in the worst case that these methods complete
* but some downstream method in the same call chain fails, it won't hurt anything to pessimistically
* reload the caches. By having these methods execute in their own transaction, it further reduces
* database row contention because any locks held in larger flows that called into these methods no longer
* require holding these locks as part of their processing.
*/
@Stateless
public class StatusManagerBean implements StatusManagerLocal {
private final Log log = LogFactory.getLog(StatusManagerBean.class);
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
@EJB
AgentManagerLocal agentManager;
@EJB
//@IgnoreDependency
ServerManagerLocal serverManager;
@EJB
//@IgnoreDependency
TopologyManagerLocal topologyManager;
@EJB
//@IgnoreDependency
AlertDefinitionManagerLocal alertDefinitionManager;
@SuppressWarnings("unchecked")
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public List<Integer> getAndClearAgentsWithStatusForServer(String serverName) {
Query selectQuery = entityManager.createNamedQuery(Agent.QUERY_FIND_ALL_WITH_STATUS_BY_SERVER);
selectQuery.setParameter("serverName", serverName);
List<Integer> agentIds = selectQuery.getResultList();
if (agentIds.size() > 0) {
// handle the oracle 1000 member IN clause issue
final int ORACLE_BATCH_SIZE = 1000;
int numAgents = agentIds.size();
int batches = (numAgents / ORACLE_BATCH_SIZE) + 1;
// iterate over the agent ids when we have more than 1000 of them
for (int batch = 0; batch < batches; ++batch) {
int fromIndex = batch * ORACLE_BATCH_SIZE;
int toIndex = fromIndex + ORACLE_BATCH_SIZE;
if (toIndex > numAgents) // don't run over the end of the list
toIndex = numAgents;
List<Integer> agentIdBatch = agentIds.subList(fromIndex, toIndex);
if (fromIndex == toIndex)
continue;
Query updateQuery = entityManager.createNamedQuery(Agent.QUERY_UPDATE_CLEAR_STATUS_BY_IDS);
updateQuery.setParameter("agentIds", agentIdBatch);
int numUpdated = updateQuery.executeUpdate();
}
}
return agentIds;
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void updateByResource(Subject subject, int resourceId) {
log.debug("About to mark status by resource");
/*
* the old alert definition is needed to know which caches to remove stale entries from; the updated / new
* alert definition is needed to know which caches need to be reloaded to get the new conditions; by the time
* this method is called, we only have the updated alert definition, thus it's not possible to intelligently
* know which of the two caches to reload; so, we need to reload them both to be sure the system is consistent
*/
markGlobalCache(); // use local references to execute in the same transaction
Query updateAgentQuery = entityManager.createNamedQuery(Agent.QUERY_UPDATE_STATUS_BY_RESOURCE);
updateAgentQuery.setParameter("resourceId", resourceId);
int agentsUpdated = updateAgentQuery.executeUpdate();
/*
* this is informational debugging only - do NOT change the status bits here
*/
if (log.isDebugEnabled()) {
Agent agent = agentManager.getAgentByResourceId(LookupUtil.getSubjectManager().getOverlord(), resourceId);
log.debug("Marking status, agent[id=" + agent.getId() + ", status=" + agent.getStatus()
+ "] for resource[id=" + resourceId + "]");
log.debug("Agents updated: " + agentsUpdated);
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void updateByAlertDefinition(Subject subject, int alertDefinitionId) {
log.debug("About to mark status by alert definition");
// alert templates and group alert definitions do not represent cache-ready entries
if (alertDefinitionManager.isResourceAlertDefinition(alertDefinitionId) == false) {
return;
}
/*
* the old alert definition is needed to know which caches to remove stale entries from; the updated / new
* alert definition is needed to know which caches need to be reloaded to get the new conditions; by the time
* this method is called, we only have the updated alert definition, thus it's not possible to intelligently
* know which of the two caches to reload; so, we need to reload them both to be sure the system is consistent
*/
markGlobalCache(); // use local references to execute in the same transaction
Query updateAgentQuery = entityManager.createNamedQuery(Agent.QUERY_UPDATE_STATUS_BY_ALERT_DEFINITION);
updateAgentQuery.setParameter("alertDefinitionId", alertDefinitionId);
int agentsUpdated = updateAgentQuery.executeUpdate();
/*
* this is informational debugging only - do NOT change the status bits here
*/
if (log.isDebugEnabled()) {
AlertDefinition definition = entityManager.find(AlertDefinition.class, alertDefinitionId);
Agent agent = agentManager.getAgentByResourceId(LookupUtil.getSubjectManager().getOverlord(), definition
.getResource().getId());
log.debug("Marking status, agent[id=" + agent.getId() + ", status=" + agent.getStatus()
+ "] for alertDefinition[id=" + alertDefinitionId + "]");
log.debug("Agents updated: " + agentsUpdated);
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void markGlobalCache() {
Query updateServerQuery = entityManager.createNamedQuery(Server.QUERY_UPDATE_STATUS_BY_NAME);
int serversUpdated = updateServerQuery.executeUpdate();
/*
* this is informational debugging only - do NOT change the status bits here
*/
if (log.isDebugEnabled()) {
Server server = serverManager.getServer();
log.debug("Marking status, server[id=" + server.getId() + ", status=" + server.getStatus() + "]");
log.debug("Servers updated: " + serversUpdated);
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void updateByMeasurementBaseline(int baselineId) {
log.debug("About to mark status by measurement baseline");
// baselines refer to measurement-based alert conditions, thus only agent statuses need to be set
Query updateAgentQuery = entityManager.createNamedQuery(Agent.QUERY_UPDATE_STATUS_BY_MEASUREMENT_BASELINE);
updateAgentQuery.setParameter("baselineId", baselineId);
updateAgentQuery.executeUpdate();
/*
* this is informational debugging only - do NOT change the status bits here
*/
if (log.isDebugEnabled()) {
MeasurementBaseline baseline = entityManager.find(MeasurementBaseline.class, baselineId);
Agent agent = baseline.getSchedule().getResource().getAgent();
log.debug("Marking status, agent[id=" + agent.getId() + ", status=" + agent.getStatus()
+ "] for measurementBaseline[id=" + baselineId + "]");
}
}
/*
* this is used to reload the caches because some resource was just imported by a discovery report,
* which requires reloading the cache because alert templates would have been applied to resources;
* we know we don't need to call updateByResource(List<Integer> resourceIds) because a discovery report
* only ever contains resources from the same agent so we can shortcut the work and call out to update
* the agent a single time
*
* the agent status no longer has to be updated when a resource is uninventoried because the out-of-band alerts
* processor (AlertsConditionConsumerBean) handles data coming across the line for resources that have either
* been deleted or uninventoried.
*/
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void updateByAgent(int agentId) {
log.debug("About to mark status by agent");
Query updateAgentQuery = entityManager.createNamedQuery(Agent.QUERY_UPDATE_STATUS_BY_AGENT);
updateAgentQuery.setParameter("agentId", agentId);
updateAgentQuery.executeUpdate();
/*
* this is informational debugging only - do NOT change the status bits here
*/
if (log.isDebugEnabled()) {
Agent agent = entityManager.find(Agent.class, agentId);
log.debug("Marking status, agent[id=" + agent.getId() + ", status=" + agent.getStatus() + "]");
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void updateByAutoBaselineCalculationJob() {
log.debug("About to mark status by autoBaselineCalculationJob");
// baselines refer to measurement-based alert conditions, thus only agent statuses need to be set
Query updateAgentQuery = entityManager.createNamedQuery(Agent.QUERY_UPDATE_STATUS_FOR_ALL);
updateAgentQuery.executeUpdate();
/*
* this is informational debugging only - do NOT change the status bits here
*/
if (log.isDebugEnabled()) {
List<Agent> agents = agentManager.getAllAgents();
for (Agent agent : agents) {
log.debug("Marking status, agent[id=" + agent.getId() + ", status=" + agent.getStatus()
+ "] for AutoBaselineCalculationJob");
}
}
}
}