Add ingest cancellation reasons; fix job startup bug

This commit is contained in:
Richard Cordovano 2015-11-22 18:12:14 -05:00
parent 2bc998155f
commit 4d19cf1e83
5 changed files with 159 additions and 41 deletions

View File

@ -73,6 +73,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
@ -327,7 +328,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}); });
IngestManager.getInstance().cancelAllIngestJobs(); IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED);
doCaseChange(null); //closes windows, etc doCaseChange(null); //closes windows, etc
if (null != oldCase.tskErrorReporter) { if (null != oldCase.tskErrorReporter) {
oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case

View File

@ -118,6 +118,8 @@ final class DataSourceIngestJob {
*/ */
private volatile boolean currentDataSourceIngestModuleCancelled; private volatile boolean currentDataSourceIngestModuleCancelled;
private volatile boolean cancelled; private volatile boolean cancelled;
private volatile IngestJob.CancellationReason cancellationReason = IngestJob.CancellationReason.NOT_CANCELLED;
private final Object cancellationStateMonitor = new Object();
private final List<String> cancelledDataSourceIngestModules = new CopyOnWriteArrayList<>(); private final List<String> cancelledDataSourceIngestModules = new CopyOnWriteArrayList<>();
/** /**
@ -160,8 +162,8 @@ final class DataSourceIngestJob {
* Constructs an object that encapsulates a data source and the ingest * Constructs an object that encapsulates a data source and the ingest
* module pipelines used to process it. * module pipelines used to process it.
* *
* @param parentJob The ingest job of which this data source ingest job is a * @param parentJob The ingest job of which this data source ingest
* part. * job is a part.
* @param dataSource The data source to be ingested. * @param dataSource The data source to be ingested.
* @param settings The settings for the ingest job. * @param settings The settings for the ingest job.
* @param runInteractively Whether or not this job should use NetBeans * @param runInteractively Whether or not this job should use NetBeans
@ -253,8 +255,8 @@ final class DataSourceIngestJob {
* *
* @param ingestModuleTemplates A mapping of ingest module factory class * @param ingestModuleTemplates A mapping of ingest module factory class
* names to ingest module templates. * names to ingest module templates.
* @param pipelineConfig An ordered list of ingest module factory class * @param pipelineConfig An ordered list of ingest module factory
* names representing an ingest pipeline. * class names representing an ingest pipeline.
* *
* @return An ordered list of ingest module templates, i.e., an * @return An ordered list of ingest module templates, i.e., an
* uninstantiated pipeline. * uninstantiated pipeline.
@ -498,7 +500,7 @@ final class DataSourceIngestJob {
String dialogTitle = NbBundle.getMessage(DataSourceIngestJob.this.getClass(), "IngestJob.cancellationDialog.title"); String dialogTitle = NbBundle.getMessage(DataSourceIngestJob.this.getClass(), "IngestJob.cancellationDialog.title");
JOptionPane.showConfirmDialog(null, panel, dialogTitle, JOptionPane.OK_OPTION, JOptionPane.PLAIN_MESSAGE); JOptionPane.showConfirmDialog(null, panel, dialogTitle, JOptionPane.OK_OPTION, JOptionPane.PLAIN_MESSAGE);
if (panel.cancelAllDataSourceIngestModules()) { if (panel.cancelAllDataSourceIngestModules()) {
DataSourceIngestJob.this.cancel(); DataSourceIngestJob.this.cancel(IngestJob.CancellationReason.USER_CANCELLED);
} else { } else {
DataSourceIngestJob.this.cancelCurrentDataSourceIngestModule(); DataSourceIngestJob.this.cancelCurrentDataSourceIngestModule();
} }
@ -527,7 +529,7 @@ final class DataSourceIngestJob {
// the cancel button on the progress bar and the OK button // the cancel button on the progress bar and the OK button
// of a cancelation confirmation dialog supplied by // of a cancelation confirmation dialog supplied by
// NetBeans. // NetBeans.
DataSourceIngestJob.this.cancel(); DataSourceIngestJob.this.cancel(IngestJob.CancellationReason.USER_CANCELLED);
return true; return true;
} }
}); });
@ -672,8 +674,9 @@ final class DataSourceIngestJob {
* @param task A file ingest task. * @param task A file ingest task.
* *
* @throws InterruptedException if the thread executing this code is * @throws InterruptedException if the thread executing this code is
* interrupted while blocked on taking from or putting to the file ingest * interrupted while blocked on taking from or
* pipelines collection. * putting to the file ingest pipelines
* collection.
*/ */
void process(FileIngestTask task) throws InterruptedException { void process(FileIngestTask task) throws InterruptedException {
try { try {
@ -910,8 +913,10 @@ final class DataSourceIngestJob {
/** /**
* Requests cancellation of ingest, i.e., a shutdown of the data source * Requests cancellation of ingest, i.e., a shutdown of the data source
* level and file level ingest pipelines. * level and file level ingest pipelines.
*
* @param reason The cancellation reason.
*/ */
void cancel() { void cancel(IngestJob.CancellationReason reason) {
if (this.doUI) { if (this.doUI) {
/** /**
* Put a cancellation message on data source level ingest progress * Put a cancellation message on data source level ingest progress
@ -951,7 +956,14 @@ final class DataSourceIngestJob {
} }
} }
synchronized (cancellationStateMonitor) {
/*
* These fields are volatile for reading, synchronized on the
* monitor here for writing.
*/
this.cancelled = true; this.cancelled = true;
this.cancellationReason = reason;
}
/** /**
* Tell the task scheduler to cancel all pending tasks, i.e., tasks not * Tell the task scheduler to cancel all pending tasks, i.e., tasks not
@ -983,6 +995,15 @@ final class DataSourceIngestJob {
return this.cancelled; return this.cancelled;
} }
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
IngestJob.CancellationReason getCancellationReason() {
return this.cancellationReason;
}
/** /**
* Write ingest module errors to the log. * Write ingest module errors to the log.
* *
@ -1019,6 +1040,7 @@ final class DataSourceIngestJob {
private final long estimatedFilesToProcess; private final long estimatedFilesToProcess;
private final IngestTasksScheduler.IngestJobTasksSnapshot tasksSnapshot; private final IngestTasksScheduler.IngestJobTasksSnapshot tasksSnapshot;
private final boolean jobCancelled; private final boolean jobCancelled;
private final IngestJob.CancellationReason jobCancellationReason;
private final List<String> cancelledDataSourceModules; private final List<String> cancelledDataSourceModules;
/** /**
@ -1047,6 +1069,7 @@ final class DataSourceIngestJob {
} }
this.jobCancelled = cancelled; this.jobCancelled = cancelled;
this.jobCancellationReason = cancellationReason;
this.cancelledDataSourceModules = new ArrayList<>(DataSourceIngestJob.this.cancelledDataSourceIngestModules); this.cancelledDataSourceModules = new ArrayList<>(DataSourceIngestJob.this.cancelledDataSourceIngestModules);
if (getIngestTasksSnapshot) { if (getIngestTasksSnapshot) {
@ -1185,6 +1208,15 @@ final class DataSourceIngestJob {
return this.jobCancelled; return this.jobCancelled;
} }
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
IngestJob.CancellationReason getCancellationReason() {
return this.jobCancellationReason;
}
/** /**
* Gets a list of the display names of any canceled data source level * Gets a list of the display names of any canceled data source level
* ingest modules * ingest modules

View File

@ -37,12 +37,26 @@ import org.sleuthkit.datamodel.Content;
*/ */
public final class IngestJob { public final class IngestJob {
/*
* An ingest job can be cancelled for various reasons.
*/
public enum CancellationReason {
NOT_CANCELLED,
USER_CANCELLED,
INGEST_MODULES_STARTUP_FAILED,
OUT_OF_DISK_SPACE,
SERVICES_DOWN,
CASE_CLOSED
}
private static final AtomicLong nextId = new AtomicLong(0L); private static final AtomicLong nextId = new AtomicLong(0L);
private final long id; private final long id;
private final Map<Long, DataSourceIngestJob> dataSourceJobs; private final Map<Long, DataSourceIngestJob> dataSourceJobs;
private final AtomicInteger incompleteJobsCount; private final AtomicInteger incompleteJobsCount;
private boolean started; // Guarded by this private boolean started;
private volatile boolean cancelled; private boolean cancelled;
private CancellationReason cancellationReason;
/** /**
* Constructs an ingest job that runs a collection of data sources through a * Constructs an ingest job that runs a collection of data sources through a
@ -61,6 +75,7 @@ public final class IngestJob {
this.dataSourceJobs.put(dataSourceIngestJob.getId(), dataSourceIngestJob); this.dataSourceJobs.put(dataSourceIngestJob.getId(), dataSourceIngestJob);
} }
incompleteJobsCount = new AtomicInteger(dataSourceJobs.size()); incompleteJobsCount = new AtomicInteger(dataSourceJobs.size());
cancellationReason = CancellationReason.NOT_CANCELLED;
} }
/** /**
@ -108,19 +123,32 @@ public final class IngestJob {
} }
started = true; started = true;
List<DataSourceIngestJob> startedDataSourceJobs = new ArrayList<>(); /*
* Try to start each data source ingest job. Note that there is a not
* unwarranted assumption here that if there is going to be a module
* startup failure, it will be for the first data source ingest job.
*
* TODO (RC): Consider separating module start up from pipeline startup
* so that no processing is done if this assumption is false.
*/
for (DataSourceIngestJob dataSourceJob : this.dataSourceJobs.values()) { for (DataSourceIngestJob dataSourceJob : this.dataSourceJobs.values()) {
errors.addAll(dataSourceJob.start()); errors.addAll(dataSourceJob.start());
if (errors.isEmpty()) { if (errors.isEmpty() == false) {
IngestManager.getInstance().fireDataSourceAnalysisStarted(id, dataSourceJob.getId(), dataSourceJob.getDataSource());
startedDataSourceJobs.add(dataSourceJob);
} else {
startedDataSourceJobs.stream().forEach((startedDataSourceJob) -> {
startedDataSourceJob.cancel();
});
break; break;
} }
} }
/*
* Handle start up success or failure.
*/
if (errors.isEmpty()) {
for (DataSourceIngestJob dataSourceJob : this.dataSourceJobs.values()) {
IngestManager.getInstance().fireDataSourceAnalysisStarted(id, dataSourceJob.getId(), dataSourceJob.getDataSource());
}
} else {
cancel(CancellationReason.INGEST_MODULES_STARTUP_FAILED);
}
return errors; return errors;
} }
@ -161,13 +189,37 @@ public final class IngestJob {
* unfinished tasks and stopping the ingest pipelines. Returns immediately, * unfinished tasks and stopping the ingest pipelines. Returns immediately,
* but there may be a delay before all of the ingest modules in the * but there may be a delay before all of the ingest modules in the
* pipelines respond by stopping processing. * pipelines respond by stopping processing.
*
* @deprecated Use cancel(CancellationReason reason) instead
*/ */
@Deprecated
synchronized public void cancel() { synchronized public void cancel() {
IngestManager ingestManager = IngestManager.getInstance(); cancel(CancellationReason.USER_CANCELLED);
}
/**
* Requests cancellation of this ingest job, which means discarding
* unfinished tasks and stopping the ingest pipelines. Returns immediately,
* but there may be a delay before all of the ingest modules in the
* pipelines respond by stopping processing.
*
* @param reason The reason for cancellation.
*/
synchronized public void cancel(CancellationReason reason) {
this.dataSourceJobs.values().stream().forEach((job) -> { this.dataSourceJobs.values().stream().forEach((job) -> {
job.cancel(); job.cancel(reason);
}); });
this.cancelled = true; this.cancelled = true;
this.cancellationReason = reason;
}
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
synchronized public CancellationReason getCancellationReason() {
return this.cancellationReason;
} }
/** /**
@ -176,7 +228,7 @@ public final class IngestJob {
* *
* @return True or false. * @return True or false.
*/ */
public boolean isCancelled() { synchronized public boolean isCancelled() {
return this.cancelled; return this.cancelled;
} }
@ -208,6 +260,7 @@ public final class IngestJob {
private boolean fileIngestRunning; private boolean fileIngestRunning;
private Date fileIngestStartTime; private Date fileIngestStartTime;
private final boolean jobCancelled; private final boolean jobCancelled;
private final IngestJob.CancellationReason jobCancellationReason;
/** /**
* A snapshot of the progress of an ingest job on the processing of a * A snapshot of the progress of an ingest job on the processing of a
@ -241,6 +294,15 @@ public final class IngestJob {
return snapshot.isCancelled(); return snapshot.isCancelled();
} }
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
synchronized public CancellationReason getCancellationReason() {
return snapshot.getCancellationReason();
}
/** /**
* Gets a list of the display names of any canceled data source * Gets a list of the display names of any canceled data source
* level ingest modules. * level ingest modules.
@ -280,6 +342,7 @@ public final class IngestJob {
} }
} }
this.jobCancelled = cancelled; this.jobCancelled = cancelled;
this.jobCancellationReason = cancellationReason;
} }
/** /**
@ -312,8 +375,8 @@ public final class IngestJob {
} }
/** /**
* Queries whether or not a cancellation request had been issued at the * Queries whether or not an ingest job level cancellation request had
* time the snapshot was taken. * been issued at the time the snapshot was taken.
* *
* @return True or false. * @return True or false.
*/ */
@ -321,6 +384,15 @@ public final class IngestJob {
return this.jobCancelled; return this.jobCancelled;
} }
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
synchronized public CancellationReason getCancellationReason() {
return this.jobCancellationReason;
}
/** /**
* Gets snapshots of the progress processing individual data sources. * Gets snapshots of the progress processing individual data sources.
* *

View File

@ -357,7 +357,7 @@ public class IngestManager {
} }
// cancel ingest if running // cancel ingest if running
cancelAllIngestJobs(); cancelAllIngestJobs(IngestJob.CancellationReason.SERVICES_DOWN);
} }
} }
}; };
@ -627,8 +627,21 @@ public class IngestManager {
/** /**
* Cancels all ingest jobs in progress. * Cancels all ingest jobs in progress.
*
* @deprecated Use cancelAllIngestJobs(IngestJob.CancellationReason reason)
* instead.
*/ */
@Deprecated
public synchronized void cancelAllIngestJobs() { public synchronized void cancelAllIngestJobs() {
cancelAllIngestJobs(IngestJob.CancellationReason.USER_CANCELLED);
}
/**
* Cancels all ingest jobs in progress.
*
* @param reason The cancellation reason.
*/
public synchronized void cancelAllIngestJobs(IngestJob.CancellationReason reason) {
// Stop creating new ingest jobs. // Stop creating new ingest jobs.
for (Future<Void> handle : ingestJobStarters.values()) { for (Future<Void> handle : ingestJobStarters.values()) {
handle.cancel(true); handle.cancel(true);
@ -636,7 +649,7 @@ public class IngestManager {
// Cancel all the jobs already created. // Cancel all the jobs already created.
for (IngestJob job : this.jobsById.values()) { for (IngestJob job : this.jobsById.values()) {
job.cancel(); job.cancel(reason);
} }
} }

View File

@ -196,7 +196,7 @@ public final class IngestMonitor {
/* /*
* Shut down ingest by cancelling all ingest jobs. * Shut down ingest by cancelling all ingest jobs.
*/ */
manager.cancelAllIngestJobs(); manager.cancelAllIngestJobs(IngestJob.CancellationReason.OUT_OF_DISK_SPACE);
String diskPath = root.getAbsolutePath(); String diskPath = root.getAbsolutePath();
IngestServices.getInstance().postMessage(IngestMessage.createManagerErrorMessage( IngestServices.getInstance().postMessage(IngestMessage.createManagerErrorMessage(
NbBundle.getMessage(this.getClass(), "IngestMonitor.mgrErrMsg.lowDiskSpace.title", diskPath), NbBundle.getMessage(this.getClass(), "IngestMonitor.mgrErrMsg.lowDiskSpace.title", diskPath),