/*
* RHQ Management Platform
* Copyright (C) 2011 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.drift;
import static java.util.Arrays.asList;
import static javax.ejb.TransactionAttributeType.NOT_SUPPORTED;
import static javax.ejb.TransactionAttributeType.REQUIRES_NEW;
import static org.rhq.core.domain.drift.DriftChangeSetCategory.COVERAGE;
import static org.rhq.core.domain.drift.DriftComplianceStatus.IN_COMPLIANCE;
import static org.rhq.core.domain.drift.DriftComplianceStatus.OUT_OF_COMPLIANCE_DRIFT;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Hibernate;
import org.jboss.remoting.CannotConnectException;
import org.rhq.core.clientapi.agent.drift.DriftAgentService;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.common.EntityContext;
import org.rhq.core.domain.criteria.DriftChangeSetCriteria;
import org.rhq.core.domain.criteria.DriftCriteria;
import org.rhq.core.domain.criteria.DriftDefinitionCriteria;
import org.rhq.core.domain.criteria.GenericDriftChangeSetCriteria;
import org.rhq.core.domain.criteria.GenericDriftCriteria;
import org.rhq.core.domain.drift.Drift;
import org.rhq.core.domain.drift.DriftCategory;
import org.rhq.core.domain.drift.DriftChangeSet;
import org.rhq.core.domain.drift.DriftChangeSetCategory;
import org.rhq.core.domain.drift.DriftComplianceStatus;
import org.rhq.core.domain.drift.DriftComposite;
import org.rhq.core.domain.drift.DriftConfigurationDefinition;
import org.rhq.core.domain.drift.DriftConfigurationDefinition.DriftHandlingMode;
import org.rhq.core.domain.drift.DriftDefinition;
import org.rhq.core.domain.drift.DriftDefinition.BaseDirectory;
import org.rhq.core.domain.drift.DriftDefinitionComparator;
import org.rhq.core.domain.drift.DriftDefinitionComparator.CompareMode;
import org.rhq.core.domain.drift.DriftDefinitionComposite;
import org.rhq.core.domain.drift.DriftDefinitionTemplate;
import org.rhq.core.domain.drift.DriftDetails;
import org.rhq.core.domain.drift.DriftFile;
import org.rhq.core.domain.drift.DriftSnapshot;
import org.rhq.core.domain.drift.DriftSnapshotRequest;
import org.rhq.core.domain.drift.FileDiffReport;
import org.rhq.core.domain.drift.Filter;
import org.rhq.core.domain.drift.dto.DriftChangeSetDTO;
import org.rhq.core.domain.drift.dto.DriftDTO;
import org.rhq.core.domain.drift.dto.DriftFileDTO;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.domain.util.PageOrdering;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.agentclient.AgentClient;
import org.rhq.enterprise.server.alert.engine.AlertConditionCacheManagerLocal;
import org.rhq.enterprise.server.alert.engine.AlertConditionCacheStats;
import org.rhq.enterprise.server.auth.SubjectManagerLocal;
import org.rhq.enterprise.server.authz.AuthorizationManagerLocal;
import org.rhq.enterprise.server.authz.PermissionException;
import org.rhq.enterprise.server.core.AgentManagerLocal;
import org.rhq.enterprise.server.plugin.pc.MasterServerPluginContainer;
import org.rhq.enterprise.server.plugin.pc.drift.DriftChangeSetSummary;
import org.rhq.enterprise.server.plugin.pc.drift.DriftServerPluginContainer;
import org.rhq.enterprise.server.plugin.pc.drift.DriftServerPluginFacet;
import org.rhq.enterprise.server.plugin.pc.drift.DriftServerPluginManager;
import org.rhq.enterprise.server.util.CriteriaQueryGenerator;
import org.rhq.enterprise.server.util.CriteriaQueryRunner;
import org.rhq.enterprise.server.util.LookupUtil;
import difflib.DiffUtils;
import difflib.Patch;
/**
* The SLSB supporting Drift management to clients.
*
* Wrappers are provided for the methods defined in DriftServerPluginFacet and the work is deferred to the plugin
* No assumption is made about the any back end implementation of a drift server plugin and therefore does not
* declare any transactioning (the NOT_SUPPORTED transaction attribute is used for all wrappers).
*
* For methods not deferred to the server plugin, the implementations are done here.
*
* @author John Sanda
* @author John Mazzitelli
* @author Jay Shaughnessy
*/
@Stateless
public class DriftManagerBean implements DriftManagerLocal, DriftManagerRemote {
// TODO Should security checks be handled here instead of delegating to the drift plugin?
// Currently any security checks that need to be performed are delegated to the plugin.
// This is fine *so far* since the only plugin is the default which is our existing SLSB
// layer backed by the RHQ database. If the plugins are only supposed to be responsible
// for persistence management of drift entities and content, then they should not be
// responsible for other concerns like security.
private Log log = LogFactory.getLog(DriftManagerBean.class);
@javax.annotation.Resource(mappedName = "java:/JmsXA")
private ConnectionFactory factory;
@javax.annotation.Resource(mappedName = "java:/queue/DriftChangesetQueue")
private Queue changesetQueue;
@javax.annotation.Resource(mappedName = "java:/queue/DriftFileQueue")
private Queue fileQueue;
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
@EJB
private AgentManagerLocal agentManager;
@EJB
private DriftManagerLocal driftManager; // ourself
@EJB
private SubjectManagerLocal subjectManager;
@EJB
private AlertConditionCacheManagerLocal alertConditionCacheManager;
@EJB
private AuthorizationManagerLocal authorizationManager;
// use a new transaction when putting things on the JMS queue. see
// http://management-platform.blogspot.com/2008/11/transaction-recovery-in-jbossas.html
@Override
@TransactionAttribute(REQUIRES_NEW)
public void addChangeSet(Subject subject, int resourceId, long zipSize, InputStream zipStream) throws Exception {
authorizeOrFail(subject, resourceId, "Can not update drifts");
Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(changesetQueue);
ObjectMessage msg = session.createObjectMessage(new DriftUploadRequest(resourceId, zipSize, zipStream));
producer.send(msg);
connection.close();
}
// use a new transaction when putting things on the JMS queue. see
// http://management-platform.blogspot.com/2008/11/transaction-recovery-in-jbossas.html
@Override
@TransactionAttribute(REQUIRES_NEW)
public void addFiles(Subject subject, int resourceId, String driftDefName, String token, long zipSize,
InputStream zipStream) throws Exception {
authorizeOrFail(subject, resourceId, "Can not update drifts");
Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(fileQueue);
ObjectMessage msg = session.createObjectMessage(new DriftUploadRequest(resourceId, driftDefName, token,
zipSize, zipStream));
producer.send(msg);
connection.close();
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public void saveChangeSetContent(Subject subject, int resourceId, String driftDefName, String token,
File changeSetFilesZip) throws Exception {
authorizeOrFail(subject, resourceId, "Can not update drifts");
saveChangeSetFiles(subject, changeSetFilesZip);
AgentClient agent = agentManager.getAgentClient(subjectManager.getOverlord(), resourceId);
DriftAgentService driftService = agent.getDriftAgentService();
driftService.ackChangeSetContent(resourceId, driftDefName, token);
}
@Override
public void processRepeatChangeSet(int resourceId, String driftDefName, int version) {
Subject overlord = subjectManager.getOverlord();
DriftDefinitionCriteria driftDefCriteria = new DriftDefinitionCriteria();
driftDefCriteria.setStrict(true);
driftDefCriteria.addFilterResourceIds(resourceId);
driftDefCriteria.addFilterName(driftDefName);
PageList<DriftDefinition> defs = findDriftDefinitionsByCriteria(overlord, driftDefCriteria);
if (defs.isEmpty()) {
log.warn("Cannot process repeat change set. No drift definition found for [resourceId: " + resourceId
+ ", driftDefinitionName: " + driftDefName + "]");
}
DriftDefinition driftDef = defs.get(0);
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
GenericDriftChangeSetCriteria criteria = new GenericDriftChangeSetCriteria();
criteria.addFilterResourceId(resourceId);
criteria.addFilterDriftDefinitionId(driftDef.getId());
criteria.addFilterVersion(Integer.toString(version));
criteria.fetchDrifts(true);
PageList<? extends DriftChangeSet<?>> changeSets = driftServerPlugin.findDriftChangeSetsByCriteria(overlord,
criteria);
if (changeSets.isEmpty()) {
log.warn("Cannot process repeat change set. No change set found for [driftDefinitionId: "
+ driftDef.getId() + ", version: " + version + "]");
return;
}
DriftChangeSet<?> changeSet = changeSets.get(0);
DriftChangeSetSummary summary = new DriftChangeSetSummary();
summary.setCategory(changeSet.getCategory());
// TODO ctime should come from agent
summary.setCreatedTime(System.currentTimeMillis());
summary.setResourceId(changeSet.getResourceId());
summary.setDriftDefinitionName(driftDef.getName());
summary.setDriftHandlingMode(driftDef.getDriftHandlingMode());
for (Drift<?, ?> drift : changeSet.getDrifts()) {
summary.addDriftPathname(drift.getPath());
}
notifyAlertConditionCacheManager("processRepeatChangeSet", summary);
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public DriftSnapshot getSnapshot(Subject subject, DriftSnapshotRequest request) {
DriftSnapshot result = new DriftSnapshot(request);
int startVersion = request.getStartVersion();
if (0 == startVersion) {
DriftChangeSet<? extends Drift<?, ?>> initialChangeset = loadInitialChangeSet(subject, request);
if (null == initialChangeset) {
if (log.isDebugEnabled()) {
log.debug("Cannot create snapshot, no initial changeset for: " + request);
}
return result;
} else {
result.addChangeSet(initialChangeset);
}
// if startVersion and endVersion are both zero, we are done.
if (Integer.valueOf(0).equals(request.getVersion())) {
return result;
}
++startVersion;
}
GenericDriftChangeSetCriteria criteria = new GenericDriftChangeSetCriteria();
criteria.addFilterCategory(DriftChangeSetCategory.DRIFT);
criteria.addFilterStartVersion(String.valueOf(startVersion));
if (null != request.getVersion()) {
criteria.addFilterEndVersion(Integer.toString(request.getVersion()));
}
criteria.addFilterDriftDefinitionId(request.getDriftDefinitionId());
criteria.addFilterDriftDirectory(request.getDirectory());
criteria.setStrict(true);
criteria.fetchDrifts(true);
criteria.addSortVersion(PageOrdering.ASC);
criteria.setPageControl(PageControl.getUnlimitedInstance());//disable paging as the code assumes all the results will be returned.
PageList<? extends DriftChangeSet<?>> changeSets = findDriftChangeSetsByCriteria(subject, criteria);
for (DriftChangeSet<? extends Drift<?, ?>> changeSet : changeSets) {
result.addChangeSet(changeSet);
}
return result;
}
private DriftChangeSet<? extends Drift<?, ?>> loadInitialChangeSet(Subject subject, DriftSnapshotRequest request) {
DriftChangeSetCriteria criteria = new GenericDriftChangeSetCriteria();
criteria.addFilterCategory(COVERAGE);
criteria.addFilterVersion("0");
// One of the next two filters will be null
criteria.addFilterDriftDefinitionId(request.getDriftDefinitionId());
criteria.addFilterId(request.getTemplateChangeSetId());
criteria.fetchDrifts(true);
criteria.setPageControl(PageControl.getUnlimitedInstance());//disable paging as the code assumes all the results will be returned.
PageList<? extends DriftChangeSet<?>> changeSets = findDriftChangeSetsByCriteria(subject, criteria);
if (changeSets.isEmpty()) {
return null;
}
return changeSets.get(0);
}
@Override
public void detectDrift(Subject subject, EntityContext context, DriftDefinition driftDef) {
switch (context.getType()) {
case Resource:
authorizeOrFail(subject, context, "Can not update drifts");
int resourceId = context.getResourceId();
Resource resource = entityManager.find(Resource.class, resourceId);
if (null == resource) {
throw new IllegalArgumentException("Resource not found [" + resourceId + "]");
}
AgentClient agentClient = agentManager.getAgentClient(subjectManager.getOverlord(), resourceId);
DriftAgentService service = agentClient.getDriftAgentService();
// this is a one-time on-demand call. If it fails throw an exception to make sure the user knows it
// did not happen. But clean it up a bit if it's a connect exception
try {
service.detectDrift(resourceId, driftDef);
} catch (CannotConnectException e) {
throw new IllegalStateException(
"Agent could not be reached and may be down (see server logs for more). Could not perform drift detection request ["
+ driftDef + "]");
}
break;
default:
throw new IllegalArgumentException("Entity Context Type not supported [" + context + "]");
}
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public void deleteDriftDefinition(Subject subject, EntityContext entityContext, String driftDefName) {
switch (entityContext.getType()) {
case Resource:
authorizeOrFail(subject, entityContext, "Can not delete drifts");
int resourceId = entityContext.getResourceId();
DriftDefinitionCriteria criteria = new DriftDefinitionCriteria();
criteria.addFilterName(driftDefName);
criteria.addFilterResourceIds(resourceId);
criteria.setStrict(true);
criteria.clearPaging();//disable paging as the code assumes all the results will be returned.
PageList<DriftDefinition> results = driftManager.findDriftDefinitionsByCriteria(subject, criteria);
DriftDefinition doomedDriftDef = null;
if (results != null && results.size() == 1) {
doomedDriftDef = results.get(0);
}
if (doomedDriftDef != null) {
// TODO security check!
// tell the agent first - we don't want the agent reporting on the drift def after we delete it
boolean unscheduledOnAgent = false;
try {
AgentClient agentClient = agentManager.getAgentClient(subjectManager.getOverlord(), resourceId);
DriftAgentService service = agentClient.getDriftAgentService();
service.unscheduleDriftDetection(resourceId, doomedDriftDef);
unscheduledOnAgent = true;
} catch (Exception e) {
log.warn(" Unable to inform agent of unscheduled drift detection [" + doomedDriftDef + "]", e);
}
// purge all data related to this drift definition
try {
driftManager.purgeByDriftDefinitionName(subject, resourceId, doomedDriftDef.getName());
} catch (Exception e) {
String warnMessage = "Failed to purge data for drift definition [" + driftDefName
+ "] for resource [" + resourceId + "].";
if (unscheduledOnAgent) {
warnMessage += " The agent was told to stop detecting drift for that definition.";
}
log.warn(warnMessage, e);
}
// now purge the drift def itself
driftManager.deleteResourceDriftDefinition(subject, resourceId, doomedDriftDef.getId());
} else {
throw new IllegalArgumentException("Resource does not have drift definition named [" + driftDefName
+ "]");
}
break;
default:
throw new IllegalArgumentException("Entity Context Type not supported [" + entityContext + "]");
}
}
@Override
public void deleteResourceDriftDefinition(Subject subject, int resourceId, int driftDefId) {
authorizeOrFail(subject, resourceId, "Can not delete drifts");
DriftDefinition doomed = entityManager.find(DriftDefinition.class, driftDefId);
doomed.getResource().setAgentSynchronizationNeeded();
entityManager.remove(doomed);
return;
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public PageList<? extends DriftChangeSet<?>> findDriftChangeSetsByCriteria(Subject subject,
DriftChangeSetCriteria criteria) {
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
return driftServerPlugin.findDriftChangeSetsByCriteria(subject, criteria);
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public PageList<DriftComposite> findDriftCompositesByCriteria(Subject subject, DriftCriteria criteria) {
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
return driftServerPlugin.findDriftCompositesByCriteria(subject, criteria);
}
@Override
public PageList<DriftDefinition> findDriftDefinitionsByCriteria(Subject subject, DriftDefinitionCriteria criteria) {
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
CriteriaQueryRunner<DriftDefinition> queryRunner = new CriteriaQueryRunner<DriftDefinition>(criteria,
generator, entityManager);
PageList<DriftDefinition> result = queryRunner.execute();
return result;
}
@Override
public PageList<DriftDefinitionComposite> findDriftDefinitionCompositesByCriteria(Subject subject,
DriftDefinitionCriteria criteria) {
PageList<DriftDefinition> defs = findDriftDefinitionsByCriteria(subject, criteria);
PageList<DriftDefinitionComposite> result = new PageList<DriftDefinitionComposite>(defs.getPageControl());
List<DriftDefinitionComposite> composites = new ArrayList<DriftDefinitionComposite>(defs.size());
GenericDriftChangeSetCriteria csCriteria = new GenericDriftChangeSetCriteria();
for (DriftDefinition def : defs) {
DriftDefinitionComposite composite = new DriftDefinitionComposite(def, null);
csCriteria.addFilterDriftDefinitionId(def.getId());
csCriteria.addSortVersion(PageOrdering.DESC);
csCriteria.setPageControl(PageControl.getSingleRowInstance());
PageList<? extends DriftChangeSet<?>> changeSets = findDriftChangeSetsByCriteria(subject, csCriteria);
if (!changeSets.isEmpty()) {
composite.setMostRecentChangeset(changeSets.get(0));
}
composites.add(composite);
}
result.addAll(composites);
return result;
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public PageList<? extends Drift<?, ?>> findDriftsByCriteria(Subject subject, DriftCriteria criteria) {
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
return driftServerPlugin.findDriftsByCriteria(subject, criteria);
}
@Override
public DriftDefinition getDriftDefinition(Subject subject, int driftDefId) {
DriftDefinition result = entityManager.find(DriftDefinition.class, driftDefId);
if (null == result) {
throw new IllegalArgumentException("Drift Definition Id [" + driftDefId + "] not found.");
}
// force lazy loads
result.getConfiguration().getProperties();
Hibernate.initialize(result.getResource());
Hibernate.initialize(result.getTemplate());
return result;
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public DriftFile getDriftFile(Subject subject, String hashId) throws Exception {
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
return driftServerPlugin.getDriftFile(subject, hashId);
}
@Override
@TransactionAttribute(REQUIRES_NEW)
public DriftChangeSetSummary saveChangeSet(Subject subject, int resourceId, File changeSetZip) throws Exception {
authorizeOrFail(subject, resourceId, "Can not update/create drifts");
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
DriftChangeSetSummary summary = driftServerPlugin.saveChangeSet(subject, resourceId, changeSetZip);
if (DriftHandlingMode.plannedChanges != summary.getDriftHandlingMode()) {
notifyAlertConditionCacheManager("saveChangeSet", summary);
}
DriftDefinitionCriteria criteria = new DriftDefinitionCriteria();
criteria.setStrict(true);
criteria.addFilterName(summary.getDriftDefinitionName());
criteria.addFilterResourceIds(resourceId);
criteria.clearPaging();//disable paging as the code assumes all the results will be returned.
PageList<DriftDefinition> definitions = findDriftDefinitionsByCriteria(subject, criteria);
if (definitions.isEmpty()) {
log.warn("Could not find drift definition for [resourceId: " + resourceId + ", driftDefinitionName: "
+ summary.getDriftDefinitionName() + "]. Will not be able check compliance for thiis drift definition");
} else {
updateCompliance(subject, definitions.get(0), summary);
}
return summary;
}
private void updateCompliance(Subject subject, DriftDefinition definition, DriftChangeSetSummary changeSetSummary) {
authorizeOrFail(subject, definition.getResource().getId(), "Can not update drifts");
boolean updateNeeded = false;
if (changeSetSummary.isInitialChangeSet()) {
updateNeeded = definition.getComplianceStatus() != IN_COMPLIANCE;
definition.setComplianceStatus(IN_COMPLIANCE);
}
if (definition.isPinned()) {
if (changeSetSummary.getDriftPathnames().isEmpty()) {
updateNeeded = definition.getComplianceStatus() != IN_COMPLIANCE;
definition.setComplianceStatus(IN_COMPLIANCE);
} else {
updateNeeded = definition.getComplianceStatus() == IN_COMPLIANCE;
definition.setComplianceStatus(OUT_OF_COMPLIANCE_DRIFT);
}
}
if (updateNeeded) {
updateDriftDefinition(subject, definition);
}
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public void saveChangeSetFiles(Subject subject, File changeSetFilesZip) throws Exception {
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
driftServerPlugin.saveChangeSetFiles(subject, changeSetFilesZip);
}
/**
* This purges the persisted data related to drift definition, but it does NOT talk to the agent to tell the agent
* about this nor does it actually delete the drift def itself.
*
* If you want to delete a drift definition and all that that entails, you must use
* {@link #deleteDriftDefinition(Subject, EntityContext, String)} instead.
*
* This method is really for internal use only.
*/
@Override
@TransactionAttribute(NOT_SUPPORTED)
public void purgeByDriftDefinitionName(Subject subject, int resourceId, String driftDefName) throws Exception {
authorizeOrFail(subject, resourceId, "Can not delete drifts");
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
driftServerPlugin.purgeByDriftDefinitionName(subject, resourceId, driftDefName);
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public int purgeOrphanedDriftFiles(Subject subject, long purgeMillis) {
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
return driftServerPlugin.purgeOrphanedDriftFiles(subject, purgeMillis);
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public void pinSnapshot(Subject subject, int driftDefId, int snapshotVersion) {
DriftDefinition driftDef = driftManager.getDriftDefinition(subject, driftDefId);
authorizeOrFail(subject, driftDef.getResource().getId(), "Can not update drifts");
if (driftDef.getTemplate() != null && driftDef.getTemplate().isPinned()) {
throw new IllegalArgumentException(("Cannot repin a definition that has been created from a pinned "
+ "template."));
}
driftDef.setPinned(true);
driftManager.updateDriftDefinition(subject, driftDef);
driftDef.getResource().setAgentSynchronizationNeeded();
DriftSnapshotRequest snapshotRequest = new DriftSnapshotRequest(driftDefId, snapshotVersion);
DriftSnapshot snapshot = getSnapshot(subject, snapshotRequest);
DriftChangeSetDTO changeSet = new DriftChangeSetDTO();
changeSet.setCategory(COVERAGE);
changeSet.setVersion(0);
changeSet.setDriftDefinitionId(driftDefId);
changeSet.setDriftHandlingMode(DriftHandlingMode.normal);
changeSet.setResourceId(driftDef.getResource().getId());
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
try {
driftServerPlugin.purgeByDriftDefinitionName(subject, driftDef.getResource().getId(), driftDef.getName());
persistSnapshot(subject, snapshot, changeSet);
} catch (Exception e) {
throw new RuntimeException("Failed to pin snapshot", e);
}
try {
AgentClient agent = agentManager.getAgentClient(subjectManager.getOverlord(), driftDef.getResource()
.getId());
DriftAgentService driftService = agent.getDriftAgentService();
driftService.pinSnapshot(driftDef.getResource().getId(), driftDef.getName(), snapshot);
} catch (Exception e) {
log.warn("Unable to notify agent that DriftDefinition[driftDefinitionId: " + driftDefId
+ ", driftDefinitionName: " + driftDef.getName() + "] has been pinned. The agent may be down.", e);
}
}
@TransactionAttribute(NOT_SUPPORTED)
public String persistSnapshot(Subject subject, DriftSnapshot snapshot,
DriftChangeSet<? extends Drift<?, ?>> changeSet) {
authorizeOrFail(subject, changeSet.getResourceId(), "Can not update/create drifts");
DriftChangeSetDTO changeSetDTO = new DriftChangeSetDTO();
changeSetDTO.setCategory(changeSet.getCategory());
changeSetDTO.setDriftHandlingMode(changeSet.getDriftHandlingMode());
changeSetDTO.setVersion(changeSet.getVersion());
changeSetDTO.setDriftDefinitionId(changeSet.getDriftDefinitionId());
changeSetDTO.setResourceId(changeSet.getResourceId());
Set<DriftDTO> drifts = new HashSet<DriftDTO>();
for (Drift<?, ?> drift : snapshot.getDriftInstances()) {
// we need to scrub ids and references to owning change sets
DriftDTO driftDTO = new DriftDTO();
driftDTO.setCategory(DriftCategory.FILE_ADDED);
driftDTO.setChangeSet(changeSetDTO);
driftDTO.setCtime(drift.getCtime());
driftDTO.setNewDriftFile(toDTO(drift.getNewDriftFile()));
driftDTO.setPath(drift.getPath());
driftDTO.setDirectory(drift.getDirectory());
drifts.add(driftDTO);
}
changeSetDTO.setDrifts(drifts);
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
try {
return driftServerPlugin.persistChangeSet(subject, changeSetDTO);
} catch (Exception e) {
throw new RuntimeException("Failed to pin snapshot", e);
}
}
private DriftFileDTO toDTO(DriftFile file) {
DriftFileDTO dto = new DriftFileDTO();
dto.setHashId(file.getHashId());
dto.setStatus(file.getStatus());
dto.setDataSize(file.getDataSize());
return dto;
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public String getDriftFileBits(Subject subject, String hash) {
log.debug("Retrieving drift file content for " + hash);
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
return driftServerPlugin.getDriftFileBits(subject, hash);
}
@Override
public byte[] getDriftFileAsByteArray(Subject subject, String hash) {
log.debug("Retrieving drift file content for " + hash);
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
return driftServerPlugin.getDriftFileAsByteArray(subject, hash);
}
@Override
public FileDiffReport generateUnifiedDiff(Subject subject, Drift<?, ?> drift) {
log.debug("Generating diff for " + drift);
String oldContent = getDriftFileBits(subject, drift.getOldDriftFile().getHashId());
List<String> oldList = asList(oldContent.split("\\n"));
String newContent = getDriftFileBits(subject, drift.getNewDriftFile().getHashId());
List<String> newList = asList(newContent.split("\\n"));
Patch patch = DiffUtils.diff(oldList, newList);
List<String> deltas = DiffUtils.generateUnifiedDiff(drift.getPath(), drift.getPath(), oldList, patch, 10);
return new FileDiffReport(patch.getDeltas().size(), deltas);
}
@SuppressWarnings("unchecked")
@Override
public FileDiffReport generateUnifiedDiffByIds(Subject subject, String driftId1, String driftId2) {
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
GenericDriftCriteria criteria = new GenericDriftCriteria();
criteria.addFilterId(driftId1);
criteria.setPageControl(PageControl.getSingleRowInstance());
List<? extends Drift<?, ?>> result = driftServerPlugin.findDriftsByCriteria(subject, criteria);
if (result.size() != 1) {
throw new IllegalArgumentException("Drift record not found: " + driftId1);
}
Drift drift1 = result.get(0);
criteria.addFilterId(driftId2);
criteria.setPageControl(PageControl.getSingleRowInstance());
result = driftServerPlugin.findDriftsByCriteria(subject, criteria);
if (result.size() != 1) {
throw new IllegalArgumentException("Drift record not found: " + driftId2);
}
Drift drift2 = result.get(0);
return generateUnifiedDiff(subject, drift1, drift2);
}
@Override
public FileDiffReport generateUnifiedDiff(Subject subject, Drift<?, ?> drift1, Drift<?, ?> drift2) {
DriftFile drift1File = drift1.getNewDriftFile();
String content1 = (null == drift1File) ? "" : getDriftFileBits(subject, drift1File.getHashId());
List<String> content1List = asList(content1.split("\\n"));
DriftFile drift2File = drift2.getNewDriftFile();
String content2 = (null == drift2File) ? "" : getDriftFileBits(subject, drift2File.getHashId());
List<String> content2List = asList(content2.split("\\n"));
Patch patch = DiffUtils.diff(content1List, content2List);
List<String> deltas = DiffUtils
.generateUnifiedDiff(drift1.getPath(), drift2.getPath(), content1List, patch, 10);
return new FileDiffReport(patch.getDeltas().size(), deltas);
}
@Override
public void updateDriftDefinition(Subject subject, DriftDefinition driftDefinition) {
authorizeOrFail(subject, driftDefinition.getResource().getId(), "Can not update drifts");
entityManager.merge(driftDefinition);
}
@Override
public void updateDriftDefinition(Subject subject, EntityContext entityContext, DriftDefinition driftDef) {
authorizeOrFail(subject, entityContext.getResourceId(), "Can not update drifts");
// before we do anything, validate certain field values to prevent downstream errors
validateDriftDefinition(driftDef);
switch (entityContext.getType()) {
case Resource: {
int resourceId = entityContext.getResourceId();
Resource resource = entityManager.find(Resource.class, resourceId);
if (null == resource) {
throw new IllegalArgumentException("Entity not found [" + entityContext + "]");
}
if (!isDriftMgmtSupported(resource)) {
throw new IllegalArgumentException("Cannot create drift definition. The resource type " +
resource.getResourceType() + " does not support drift management");
}
// Update or add the driftDef as necessary
DriftDefinitionComparator comparator = new DriftDefinitionComparator(
CompareMode.ONLY_DIRECTORY_SPECIFICATIONS);
boolean isUpdated = false;
for (DriftDefinition dc : resource.getDriftDefinitions()) {
if (dc.getName().equals(driftDef.getName())) {
// compare the directory specs (basedir/includes-excludes filters only - if they are different, abort.
// you cannot update drift def that changes basedir/includes/excludes from the original.
// the user must delete the drift def and create a new one, as opposed to trying to update the existing one.
if (comparator.compare(driftDef, dc) == 0) {
if (dc.isPinned() && !driftDef.isPinned()) {
dc.setComplianceStatus(DriftComplianceStatus.IN_COMPLIANCE);
}
entityManager.remove(dc.getConfiguration()); // don't orphan the config
dc.setConfiguration(driftDef.getConfiguration().deepCopyWithoutProxies());
isUpdated = true;
break;
} else {
throw new IllegalArgumentException(
"A new definition must have a unique name. An existing definition cannot update it's base directory or includes/excludes filters.");
}
}
}
if (!isUpdated) {
validateTemplateForNewDef(driftDef, resource);
resource.addDriftDefinition(driftDef);
// We call persist here because if this definition is created
// from a pinned template, then we need to generate the initial
// change set. And we need the definition id to pass to the
// drift server plugin.
entityManager.persist(driftDef);
DriftDefinitionTemplate template = driftDef.getTemplate();
if (template != null && template.isPinned()) {
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
driftServerPlugin.copyChangeSet(subject, template.getChangeSetId(), driftDef.getId(),
resourceId);
}
}
resource.setAgentSynchronizationNeeded();
AgentClient agentClient = agentManager.getAgentClient(subjectManager.getOverlord(), resourceId);
DriftAgentService service = agentClient.getDriftAgentService();
try {
DriftSnapshot snapshot = null;
if (driftDef.getTemplate() != null && driftDef.getTemplate().isPinned()) {
snapshot = getSnapshot(subject, new DriftSnapshotRequest(driftDef.getId()));
}
// Do not pass attached entities to the following Agent call, which is outside Hibernate's control. Flush
// and clear the entities to ensure the work above is captured and we pass out a detached object.
entityManager.flush();
entityManager.clear();
if (snapshot != null) {
service.updateDriftDetection(resourceId, driftDef, snapshot);
} else {
service.updateDriftDetection(resourceId, driftDef);
}
} catch (Exception e) {
log.warn(" Unable to inform agent of unscheduled drift detection [" + driftDef + "]", e);
}
break;
}
default:
throw new IllegalArgumentException("Entity Context Type not supported [" + entityContext + "]");
}
}
public static void validateDriftDefinition(DriftDefinition driftDef) {
if (!driftDef.getName().matches(DriftConfigurationDefinition.PROP_NAME_REGEX_PATTERN)) {
throw new IllegalArgumentException("Drift definition name contains invalid characters: "
+ driftDef.getName());
}
BaseDirectory baseDir = driftDef.getBasedir();
if (null == baseDir
|| !baseDir.getValueName().matches(DriftConfigurationDefinition.PROP_BASEDIR_PATH_REGEX_PATTERN)) {
throw new IllegalArgumentException(
"Drift definition base directory is null or contains invalid characters: " + baseDir);
}
List<List<Filter>> filtersList = new ArrayList<List<Filter>>(2);
filtersList.add(driftDef.getIncludes());
filtersList.add(driftDef.getExcludes());
for (List<Filter> filterList : filtersList) {
for (Filter filter : filterList) {
String path = (null == filter.getPath()) ? null : filter.getPath().trim();
if (null != path && !path.isEmpty()
&& !path.matches(DriftConfigurationDefinition.PROP_FILTER_PATH_REGEX_PATTERN)) {
throw new IllegalArgumentException("Drift definition filter path contains invalid characters: "
+ path);
}
String pattern = (null == filter.getPattern()) ? null : filter.getPattern().trim();
if (null != pattern && !pattern.isEmpty()
&& !pattern.matches(DriftConfigurationDefinition.PROP_FILTER_PATTERN_REGEX_PATTERN)) {
throw new IllegalArgumentException("Drift definition filter pattern contains invalid characters: "
+ pattern);
}
}
}
}
private boolean isDriftMgmtSupported(Resource resource) {
ResourceType type = resource.getResourceType();
return type.getDriftDefinitionTemplates() != null && !type.getDriftDefinitionTemplates().isEmpty();
}
private void validateTemplateForNewDef(DriftDefinition driftDef, Resource resource) {
if (driftDef.getTemplate() == null) {
return;
}
DriftDefinitionTemplate template = entityManager.find(DriftDefinitionTemplate.class,
driftDef.getTemplate().getId());
if (template == null) {
throw new IllegalArgumentException("Cannot create drift definition with template " +
DriftDefinitionTemplate.class.getSimpleName() + "[" + driftDef.getTemplate().getName() +
"] that has not been saved");
}
if (!template.getResourceType().equals(resource.getResourceType())) {
throw new IllegalArgumentException("Cannot create drift definition with template " +
DriftDefinitionTemplate.class.getSimpleName() + "[" + driftDef.getTemplate().getName() +
"] that is from a different resource type, " + template.getResourceType());
}
}
@Override
public boolean isBinaryFile(Subject subject, Drift<?, ?> drift) {
return DriftUtil.isBinaryFile(drift);
}
@Override
@TransactionAttribute(NOT_SUPPORTED)
public DriftDetails getDriftDetails(Subject subject, String driftId) {
log.debug("Loading drift details for drift id: " + driftId);
GenericDriftCriteria criteria = new GenericDriftCriteria();
criteria.addFilterId(driftId);
criteria.fetchChangeSet(true);
criteria.setPageControl(PageControl.getSingleRowInstance());
DriftDetails driftDetails = new DriftDetails();
DriftServerPluginFacet driftServerPlugin = getServerPlugin();
DriftFile newFile = null;
DriftFile oldFile = null;
PageList<? extends Drift<?, ?>> results = driftServerPlugin.findDriftsByCriteria(subject, criteria);
if (results.size() == 0) {
log.warn("Unable to get the drift details for drift id " + driftId
+ ". No drift object found with that id.");
return null;
}
Drift<?, ?> drift = results.get(0);
driftDetails.setDrift(drift);
try {
switch (drift.getCategory()) {
case FILE_ADDED:
newFile = driftServerPlugin.getDriftFile(subject, drift.getNewDriftFile().getHashId());
driftDetails.setNewFileStatus(newFile.getStatus());
break;
case FILE_CHANGED:
newFile = driftServerPlugin.getDriftFile(subject, drift.getNewDriftFile().getHashId());
oldFile = driftServerPlugin.getDriftFile(subject, drift.getOldDriftFile().getHashId());
driftDetails.setNewFileStatus(newFile.getStatus());
driftDetails.setOldFileStatus(oldFile.getStatus());
driftDetails.setPreviousChangeSet(loadPreviousChangeSet(subject, drift));
break;
case FILE_REMOVED:
oldFile = driftServerPlugin.getDriftFile(subject, drift.getOldDriftFile().getHashId());
driftDetails.setOldFileStatus(oldFile.getStatus());
break;
}
} catch (Exception e) {
log.error("An error occurred while loading the drift details for drift id " + driftId + ": "
+ e.getMessage());
throw new RuntimeException("An error occurred while loading th drift details for drift id " + driftId, e);
}
driftDetails.setBinaryFile(isBinaryFile(subject, drift));
return driftDetails;
}
private void notifyAlertConditionCacheManager(String callingMethod, DriftChangeSetSummary summary) {
AlertConditionCacheStats stats = alertConditionCacheManager.checkConditions(summary);
if (log.isDebugEnabled()) {
log.debug(callingMethod + ": " + stats.toString());
}
}
private DriftChangeSet<?> loadPreviousChangeSet(Subject subject, Drift<?, ?> drift) {
GenericDriftChangeSetCriteria criteria = new GenericDriftChangeSetCriteria();
criteria.addFilterResourceId(drift.getChangeSet().getResourceId());
criteria.addFilterDriftDefinitionId(drift.getChangeSet().getDriftDefinitionId());
criteria.addFilterVersion(Integer.toString(drift.getChangeSet().getVersion() - 1));
criteria.setPageControl(PageControl.getSingleRowInstance());
PageList<? extends DriftChangeSet<?>> results = findDriftChangeSetsByCriteria(subject, criteria);
// TODO handle empty results
return results.get(0);
}
private DriftServerPluginFacet getServerPlugin() {
MasterServerPluginContainer masterPC = LookupUtil.getServerPluginService().getMasterPluginContainer();
if (masterPC == null) {
log.warn(MasterServerPluginContainer.class.getSimpleName() + " is not started yet");
return null;
}
DriftServerPluginContainer pc = masterPC.getPluginContainerByClass(DriftServerPluginContainer.class);
if (pc == null) {
log.warn(DriftServerPluginContainer.class + " has not been loaded by the " + masterPC.getClass() + " yet");
return null;
}
DriftServerPluginManager pluginMgr = (DriftServerPluginManager) pc.getPluginManager();
return pluginMgr.getDriftServerPluginComponent();
}
private void authorizeOrFail(Subject subject, int resourceId, String message) {
if (!authorizationManager.hasResourcePermission(subject, Permission.MANAGE_DRIFT, resourceId)) {
throw new PermissionException(message + " - " + subject + " lacks "
+ Permission.MANAGE_DRIFT + " for resource[id=" + resourceId + "]");
}
}
private void authorizeOrFail(Subject subject, EntityContext context, String message) {
authorizeOrFail(subject, context.getResourceId(), message);
}
}