/*
* RHQ Management Platform
* Copyright (C) 2005-2014 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.discovery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.clientapi.agent.upgrade.ResourceUpgradeRequest;
import org.rhq.core.clientapi.agent.upgrade.ResourceUpgradeResponse;
import org.rhq.core.clientapi.server.discovery.DiscoveryServerService;
import org.rhq.core.clientapi.server.discovery.InvalidInventoryReportException;
import org.rhq.core.clientapi.server.discovery.InventoryReport;
import org.rhq.core.clientapi.server.discovery.StaleTypeException;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.criteria.ResourceCriteria;
import org.rhq.core.domain.discovery.AvailabilityReport;
import org.rhq.core.domain.discovery.MergeInventoryReportResults;
import org.rhq.core.domain.discovery.MergeResourceResponse;
import org.rhq.core.domain.discovery.ResourceSyncInfo;
import org.rhq.core.domain.measurement.ResourceMeasurementScheduleRequest;
import org.rhq.core.domain.resource.Agent;
import org.rhq.core.domain.resource.InventoryStatus;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceError;
import org.rhq.core.util.collection.ArrayUtils;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.enterprise.server.alert.AlertDefinitionCreationException;
import org.rhq.enterprise.server.alert.AlertTemplateManagerLocal;
import org.rhq.enterprise.server.cloud.StatusManagerLocal;
import org.rhq.enterprise.server.core.AgentManagerLocal;
import org.rhq.enterprise.server.measurement.AvailabilityManagerLocal;
import org.rhq.enterprise.server.measurement.MeasurementScheduleManagerLocal;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
import org.rhq.enterprise.server.util.LookupUtil;
import org.rhq.enterprise.server.util.concurrent.AvailabilityReportSerializer;
import org.rhq.enterprise.server.util.concurrent.InventoryReportSerializer;
/**
* This is the service that receives inventory data from agents. As agents discover resources, they report back to the
* server what they have found via this service.
*
* @author John Mazzitelli
*/
public class DiscoveryServerServiceImpl implements DiscoveryServerService {
private Log log = LogFactory.getLog(DiscoveryServerServiceImpl.class);
/**
* @see DiscoveryServerService#mergeInventoryReport(InventoryReport)
*/
@Override
public MergeInventoryReportResults mergeInventoryReport(InventoryReport report)
throws InvalidInventoryReportException, StaleTypeException {
InventoryReportSerializer.getSingleton().lock(report.getAgent().getName());
try {
long start = System.currentTimeMillis();
DiscoveryBossLocal discoveryBoss = LookupUtil.getDiscoveryBoss();
MergeInventoryReportResults results;
try {
results = discoveryBoss.mergeInventoryReport(report);
} catch (StaleTypeException e) {
// There is no need to log this exception as it is part of a normal work flow
// that occurs as a result of a user deleting a plugin. DiscoveryBossBean
// already logs a message about the stale types that can be useful for
// debugging; so, we just need to propagate the exception to the agent.
throw e;
} catch (InvalidInventoryReportException e) {
Agent agent = report.getAgent();
if (log.isDebugEnabled()) {
log.error("Received invalid inventory report from agent [" + agent + "]", e);
} else {
/*
* this is expected when the platform is uninventoried, because the agent often has in-flight reports
* going to the server at the time the platform's agent is being deleted from the database
*/
log.error("Received invalid inventory report from agent [" + agent + "]: " + e.getMessage());
}
throw e;
} catch (RuntimeException e) {
log.error("Fatal error occurred during merging of inventory report from agent [" + report.getAgent()
+ "].", e);
throw e;
}
long elapsed = (System.currentTimeMillis() - start);
if (elapsed > 30000L) {
log.warn("Performance: inventory merge (" + elapsed + ")ms");
} else {
if (log.isDebugEnabled()) {
log.debug("Performance: inventory merge (" + elapsed + ")ms");
}
}
return results;
} finally {
InventoryReportSerializer.getSingleton().unlock(report.getAgent().getName());
}
}
@Override
public Collection<ResourceSyncInfo> getResourceSyncInfo(int resourceId) {
long start = System.currentTimeMillis();
DiscoveryBossLocal discoveryBoss = LookupUtil.getDiscoveryBoss();
Collection<ResourceSyncInfo> results;
results = discoveryBoss.getResourceSyncInfo(resourceId);
long elapsed = (System.currentTimeMillis() - start);
if (elapsed > 30000L) {
log.warn("Performance: get resource sync info (" + elapsed + ")ms");
} else {
if (log.isDebugEnabled()) {
log.debug("Performance: get resource sync info (" + elapsed + ")ms");
}
}
return results;
}
@Override
public boolean mergeAvailabilityReport(AvailabilityReport availabilityReport) {
AvailabilityReportSerializer.getSingleton().lock(availabilityReport.getAgentName());
try {
String reportToString = availabilityReport.toString(false);
if (log.isDebugEnabled())
log.debug("Processing " + reportToString);
long start = System.currentTimeMillis();
AvailabilityManagerLocal availabilityManager = LookupUtil.getAvailabilityManager();
boolean ok = availabilityManager.mergeAvailabilityReport(availabilityReport);
long elapsed = (System.currentTimeMillis() - start);
if (elapsed > 20000L) {
log.warn("Performance: processed " + reportToString + " - needFull=[" + !ok + "] in (" + elapsed
+ ")ms");
} else {
if (log.isDebugEnabled()) {
log.debug("Performance: processed " + reportToString + " - needFull=[" + !ok + "] in (" + elapsed
+ ")ms");
}
}
return ok;
} catch (Exception e) {
log.info("Error processing availability report from [" + availabilityReport.getAgentName() + "]: "
+ ThrowableUtil.getAllMessages(e));
return true; // not sure what happened, but avoid infinite recursion during error conditions; do not ask for a full report
} finally {
AvailabilityReportSerializer.getSingleton().unlock(availabilityReport.getAgentName());
}
}
@Override
public Set<Resource> getResources(Set<Integer> resourceIds, boolean includeDescendants) {
long start = System.currentTimeMillis();
ResourceManagerLocal resourceManager = LookupUtil.getResourceManager();
Set<Resource> resources = new HashSet<Resource>();
for (Integer resourceId : resourceIds) {
Resource resource = resourceManager.getResourceTree(resourceId, includeDescendants);
if (isVisibleInInventory(resource)) {
resource = convertToPojoResource(resource, includeDescendants);
cleanoutResource(resource);
resources.add(resource);
}
}
if (log.isDebugEnabled()) {
log.debug("Performance: get Resources [" + resourceIds + "], recursive=" + includeDescendants
+ ", timing (" + (System.currentTimeMillis() - start) + ")ms");
}
return resources;
}
@Override
public List<Resource> getResourcesAsList(Integer... resourceIds) {
long start = System.currentTimeMillis();
ResourceCriteria criteria = new ResourceCriteria();
// get all of the resources for the supplied ids
criteria.addFilterIds(resourceIds);
// filter out any that are not actually in inventory
criteria.addFilterInventoryStatuses(new ArrayList<InventoryStatus>(InventoryStatus.getInInventorySet()));
// get all of them, don't limit to default paging
criteria.clearPaging();
criteria.fetchResourceType(true);
criteria.fetchPluginConfiguration(true);
ResourceManagerLocal resourceManager = LookupUtil.getResourceManager();
Subject overlord = LookupUtil.getSubjectManager().getOverlord();
List<Resource> result = resourceManager.findResourcesByCriteria(overlord, criteria);
if (log.isDebugEnabled()) {
log.debug("Performance: get ResourcesAsList [" + resourceIds + "], timing ("
+ (System.currentTimeMillis() - start) + ")ms");
}
// Now do some clean out of stuff the agent does not need
// Perhaps we should limit the query above to only return relevant stuff
for (Resource resource: result) {
cleanoutResource(resource);
}
return result;
}
private void cleanoutResource(Resource resource) {
resource.setAncestry(null);
resource.setAlertDefinitions(Collections.EMPTY_SET);
resource.setLocation(null);
resource.setDescription(null);
resource.setAutoGroupBackingGroups(Collections.EMPTY_LIST);
resource.setExplicitGroups(Collections.EMPTY_SET);
resource.setCreateChildResourceRequests(Collections.EMPTY_LIST);
resource.setImplicitGroups(Collections.EMPTY_SET);
resource.setInstalledPackageHistory(Collections.EMPTY_LIST);
resource.setInstalledPackages(Collections.EMPTY_SET);
resource.setPluginConfigurationUpdates(Collections.EMPTY_LIST);
resource.setResourceConfigurationUpdates(Collections.EMPTY_LIST);
if (resource.getPluginConfiguration()!=null) {
resource.getPluginConfiguration().cleanoutRawConfiguration();
}
}
@Override
public Map<Integer, InventoryStatus> getInventoryStatus(int rootResourceId, boolean descendents) {
long start = System.currentTimeMillis();
ResourceManagerLocal resourceManager = LookupUtil.getResourceManager();
Map<Integer, InventoryStatus> statuses = resourceManager.getResourceStatuses(rootResourceId, descendents);
if (log.isDebugEnabled()) {
log.debug("Performance: get inventory statuses for [" + statuses.size() + "] timing ("
+ (System.currentTimeMillis() - start) + ")ms");
}
return statuses;
}
@Override
public void setResourceError(ResourceError resourceError) {
try {
ResourceManagerLocal resourceManager = LookupUtil.getResourceManager();
resourceManager.addResourceError(resourceError);
} catch (RuntimeException re) {
log.error("Failed to persist Resource error [" + resourceError + "].", re);
throw re;
}
}
@Override
public void clearResourceConfigError(int resourceId) {
ResourceManagerLocal resourceManager = LookupUtil.getResourceManager();
resourceManager.clearResourceConfigError(resourceId);
}
@Override
public MergeResourceResponse addResource(Resource resource, int creatorSubjectId) {
DiscoveryBossLocal discoveryBoss = LookupUtil.getDiscoveryBoss();
return discoveryBoss.addResource(resource, creatorSubjectId);
}
@Override
public boolean updateResourceVersion(int resourceId, String version) {
DiscoveryBossLocal discoveryBoss = LookupUtil.getDiscoveryBoss();
return discoveryBoss.updateResourceVersion(resourceId, version);
}
@Override
public Set<ResourceUpgradeResponse> upgradeResources(Set<ResourceUpgradeRequest> upgradeRequests) {
DiscoveryBossLocal discoveryBoss = LookupUtil.getDiscoveryBoss();
return discoveryBoss.upgradeResources(upgradeRequests);
}
private static Resource convertToPojoResource(Resource resource, boolean includeDescendants) {
Resource pojoResource = new Resource(resource.getId());
pojoResource.setUuid(resource.getUuid());
pojoResource.setResourceKey(resource.getResourceKey());
pojoResource.setResourceType(resource.getResourceType());
pojoResource.setMtime(resource.getMtime());
pojoResource.setInventoryStatus(resource.getInventoryStatus());
Configuration pcCopy = resource.getPluginConfiguration();
if (pcCopy != null) {
pcCopy = pcCopy.deepCopy();
}
pojoResource.setPluginConfiguration(pcCopy);
pojoResource.setName(resource.getName());
pojoResource.setDescription(resource.getDescription());
pojoResource.setLocation(resource.getLocation());
pojoResource.setVersion(resource.getVersion());
if (resource.getParentResource() != null) {
pojoResource.setParentResource(convertToPojoResource(resource.getParentResource(), false));
}
if (includeDescendants) {
for (Resource childResource : resource.getChildResources()) {
if (isVisibleInInventory(childResource)) {
pojoResource.addChildResource(convertToPojoResource(childResource, true));
}
}
}
return pojoResource;
}
private static boolean isVisibleInInventory(Resource resource) {
return resource.getInventoryStatus() != InventoryStatus.UNINVENTORIED;
}
@Override
public Set<ResourceMeasurementScheduleRequest> postProcessNewlyCommittedResources(Set<Integer> resourceIds) {
if (log.isDebugEnabled()) {
log.debug("Post-processing " + resourceIds.size() + "newly committed resources");
log.debug("Ids were: " + resourceIds);
}
Subject overlord = LookupUtil.getSubjectManager().getOverlord();
AlertTemplateManagerLocal alertTemplateManager = LookupUtil.getAlertTemplateManager();
MeasurementScheduleManagerLocal scheduleManager = LookupUtil.getMeasurementScheduleManager();
AgentManagerLocal agentManager = LookupUtil.getAgentManager();
StatusManagerLocal statusManager = LookupUtil.getStatusManager();
long start = System.currentTimeMillis();
// do this in one fell swoop, instead of one resource at a time
Set<ResourceMeasurementScheduleRequest> results = scheduleManager.findSchedulesForResourceAndItsDescendants(
ArrayUtils.unwrapCollection(resourceIds), false);
long time = (System.currentTimeMillis() - start);
if (time >= 10000L) {
log.info("Performance: commit resource, create schedules timing: resourceCount/millis="
+ resourceIds.size() + '/' + time);
} else if (log.isDebugEnabled()) {
log.debug("Performance: commit resource, create schedules timing: resourceCount/millis="
+ resourceIds.size() + '/' + time);
}
start = System.currentTimeMillis();
for (Integer resourceId : resourceIds) {
// apply alert templates
try {
alertTemplateManager.updateAlertDefinitionsForResource(overlord, resourceId);
} catch (AlertDefinitionCreationException adce) {
/* should never happen because AlertDefinitionCreationException is only ever
* thrown if updateAlertDefinitionsForResource isn't called as the overlord
*
* but we'll log it anyway, just in case, so it isn't just swallowed
*/
log.error(adce);
} catch (Throwable t) {
log.debug("Could not apply alert templates for resourceId = " + resourceId, t);
}
}
try {
if (resourceIds.size() > 0) {
// they all come from the same agent, so pick any old one
int anyResourceIdFromNewlyCommittedSet = resourceIds.iterator().next();
int agentId = agentManager.getAgentIdByResourceId(anyResourceIdFromNewlyCommittedSet);
statusManager.updateByAgent(agentId);
}
} catch (Throwable t) {
log.debug("Could not reload caches for newly committed resources", t);
}
time = (System.currentTimeMillis() - start);
if (time >= 10000L) {
log.info("Performance: commit resource, apply alert templates timing: resourceCount/millis="
+ resourceIds.size() + '/' + time);
} else if (log.isDebugEnabled()) {
log.debug("Performance: commit resource, apply alert templates timing: resourceCount/millis="
+ resourceIds.size() + '/' + time);
}
return results;
}
@Override
public void setResourceEnablement(int resourceId, boolean setEnabled) {
ResourceManagerLocal resourceManager = LookupUtil.getResourceManager();
Subject overlord = LookupUtil.getSubjectManager().getOverlord();
if (setEnabled) {
resourceManager.enableResources(overlord, new int[] { resourceId });
} else {
resourceManager.disableResources(overlord, new int[] { resourceId });
}
}
}