1
0
mirror of https://github.com/elisspace/autopsy.git synced 2026-09-03 22:39:56 +00:00

Added support for pause/resume/shutdown to AID.

This commit is contained in:
esaunders
2018-04-25 15:23:44 -04:00
parent 5d59a5d466
commit eb83dfd0e0
8 changed files with 267 additions and 82 deletions

View File

@@ -151,9 +151,10 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer {
@Override
public void update(Observable o, Object arg) {
if (arg instanceof AutoIngestNodeState)
if (arg instanceof AutoIngestNodeState) {
EventQueue.invokeLater(() -> {
refreshTables();
});
}
}
}

View File

@@ -142,12 +142,11 @@ final class AinStatusNode extends AbstractNode {
List<Action> actions = new ArrayList<>();
if (AutoIngestDashboard.isAdminAutoIngestDashboard()) {
if (nodeState.getState() == AutoIngestNodeState.State.PAUSED_BY_REQUEST
|| nodeState.getState() == AutoIngestNodeState.State.PAUSED_DUE_TO_SYSTEM_ERROR) {
actions.add(new AutoIngestAdminActions.ResumeAction());
} else if (nodeState.getState() == AutoIngestNodeState.State.RUNNING){
actions.add(new AutoIngestAdminActions.PauseAction());
|| nodeState.getState() == AutoIngestNodeState.State.PAUSED_DUE_TO_SYSTEM_ERROR
|| nodeState.getState() == AutoIngestNodeState.State.RUNNING) {
actions.add(new AutoIngestAdminActions.AutoIngestNodeControlAction.PauseResumeAction(nodeState));
}
actions.add(new AutoIngestAdminActions.ShutdownAction());
actions.add(new AutoIngestAdminActions.AutoIngestNodeControlAction.ShutdownAction(nodeState));
}
return actions.toArray(new Action[actions.size()]);
}

View File

@@ -18,14 +18,122 @@
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestNodeState;
import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog;
final class AutoIngestAdminActions {
static abstract class AutoIngestNodeControlAction extends AbstractAction {
private final AutoIngestNodeState nodeState;
private final Logger logger = Logger.getLogger(AutoIngestNodeControlAction.class.getName());
AutoIngestNodeControlAction(AutoIngestNodeState nodeState, String title) {
super(title);
this.nodeState = nodeState;
}
@Override
public void actionPerformed(ActionEvent e) {
if (nodeState != null) {
final AinStatusDashboardTopComponent tc = (AinStatusDashboardTopComponent) WindowManager.getDefault().findTopComponent(AinStatusDashboardTopComponent.PREFERRED_ID);
if (tc != null) {
AinStatusDashboard dashboard = tc.getAinStatusDashboard();
if (dashboard != null) {
dashboard.getNodesStatusPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
EventQueue.invokeLater(() -> {
try {
controlAutoIngestNode(dashboard);
} catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
logger.log(Level.WARNING, "Error sending control event to node", ex);
} finally {
dashboard.getNodesStatusPanel().setCursor(Cursor.getDefaultCursor());
}
});
}
}
}
}
protected abstract void controlAutoIngestNode(AinStatusDashboard dashboard) throws AutoIngestMonitor.AutoIngestMonitorException;
AutoIngestNodeState getNodeState() {
return nodeState;
}
@NbBundle.Messages({"AutoIngestAdminActions.pause.title=Pause Node",
"AutoIngestAdminActions.resume.title=Resume Node"})
static final class PauseResumeAction extends AutoIngestNodeControlAction {
private static final long serialVersionUID = 1L;
PauseResumeAction(AutoIngestNodeState nodeState) {
super(nodeState, nodeState.getState() == AutoIngestNodeState.State.RUNNING
? Bundle.AutoIngestAdminActions_pause_title() : Bundle.AutoIngestAdminActions_resume_title());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
@Override
protected void controlAutoIngestNode(AinStatusDashboard dashboard) throws AutoIngestMonitor.AutoIngestMonitorException {
if (getNodeState().getState() == AutoIngestNodeState.State.RUNNING) {
dashboard.getMonitor().pauseAutoIngestNode(getNodeState().getName());
} else {
dashboard.getMonitor().resumeAutoIngestNode(getNodeState().getName());
}
}
}
@NbBundle.Messages({"AutoIngestAdminActions.shutdown.title=Shutdown Node",
"AutoIngestAdminActions.shutdown.OK=OK", "AutoIngestAdminActions.shutdown.Cancel=Cancel",
"AutoIngestAdminActions.shutdown.consequences=This will cancel any currently running job on this host. Exiting while a job is running potentially leaves the case in an inconsistent or corrupted state."})
static final class ShutdownAction extends AutoIngestNodeControlAction {
private static final long serialVersionUID = 1L;
ShutdownAction(AutoIngestNodeState nodeState) {
super(nodeState, Bundle.AutoIngestAdminActions_shutdown_title());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
@Override
protected void controlAutoIngestNode(AinStatusDashboard dashboard) throws AutoIngestMonitor.AutoIngestMonitorException {
Object[] options = {Bundle.AutoIngestAdminActions_shutdown_OK(),
Bundle.AutoIngestAdminActions_shutdown_Cancel()
};
int reply = JOptionPane.showOptionDialog(dashboard.getNodesStatusPanel(),
Bundle.AutoIngestAdminActions_shutdown_consequences(),
NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.ConfirmExitHeader"),
JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE,
null,
options,
options[JOptionPane.NO_OPTION]);
if (reply == JOptionPane.OK_OPTION) {
dashboard.getMonitor().shutdownAutoIngestNode(getNodeState().getName());
}
}
}
}
@NbBundle.Messages({"AutoIngestAdminActions.progressDialogAction.title=Ingest Progress"})
static final class ProgressDialogAction extends AbstractAction {
@@ -152,65 +260,4 @@ final class AutoIngestAdminActions {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.pause.title=Pause Node"})
static final class PauseAction extends AbstractAction {
private static final long serialVersionUID = 1L;
PauseAction() {
super(Bundle.AutoIngestAdminActions_pause_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.resume.title=Resume Node"})
static final class ResumeAction extends AbstractAction {
private static final long serialVersionUID = 1L;
ResumeAction() {
super(Bundle.AutoIngestAdminActions_resume_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.shutdown.title=Shutdown Node"})
static final class ShutdownAction extends AbstractAction {
private static final long serialVersionUID = 1L;
ShutdownAction() {
super(Bundle.AutoIngestAdminActions_shutdown_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
}

View File

@@ -852,7 +852,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
case CASE_DELETED:
updateExecutor.submit(new UpdateAllJobsTablesTask());
break;
case PAUSED_BY_REQUEST:
case PAUSED_BY_USER_REQUEST:
EventQueue.invokeLater(() -> {
tbStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.bnPause.paused"));
bnOptions.setEnabled(true);
@@ -881,6 +881,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
case JOB_STATUS_UPDATED:
updateExecutor.submit(new UpdateRunningJobsTablesTask());
break;
case SHUTTING_DOWN:
LifecycleManager.getDefault().exit();
break;
default:
break;
}

View File

@@ -170,7 +170,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa
AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status);
explorerManager.setRootContext(autoIngestNode);
outline.setRowSelectionAllowed(true);
if (selectedNodes.length > 0 && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored
if (selectedNodes.length > 0 && autoIngestNode.getChildren().findChild(selectedNodes[0].getName()) != null && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored
try {
explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())});
} catch (PropertyVetoException ignore) {

View File

@@ -93,6 +93,7 @@ import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.Shar
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.AutoIngestJobException;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason;
import org.sleuthkit.autopsy.ingest.IngestJobSettings;
@@ -133,7 +134,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
Event.JOB_COMPLETED.toString(),
Event.CASE_PRIORITIZED.toString(),
Event.JOB_STARTED.toString(),
Event.REPORT_STATE.toString()}));
Event.REPORT_STATE.toString(),
ControlEventType.PAUSE.toString(),
ControlEventType.RESUME.toString(),
ControlEventType.SHUTDOWN.toString()}));
private static final long JOB_STATUS_EVENT_INTERVAL_SECONDS = 10;
private static final String JOB_STATUS_PUBLISHING_THREAD_NAME = "AIM-job-status-event-publisher-%d";
private static final long MAX_MISSED_JOB_STATUS_UPDATES = 10;
@@ -281,6 +285,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
handleRemoteCaseDeletedEvent((AutoIngestCaseDeletedEvent) event);
} else if (event instanceof AutoIngestRequestNodeStateEvent) {
handleRemoteRequestNodeStateEvent();
} else if (event instanceof AutoIngestNodeControlEvent) {
handleRemoteNodeControlEvent((AutoIngestNodeControlEvent) event);
}
}
}
@@ -404,6 +410,28 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
eventPublisher.publishRemotely(lastPublishedStateEvent);
}
private void handleRemoteNodeControlEvent(AutoIngestNodeControlEvent event) {
if (event.getNodeName().compareToIgnoreCase(LOCAL_HOST_NAME) == 0) {
switch (event.getControlEventType()) {
case PAUSE:
pause();
break;
case RESUME:
resume();
break;
case SHUTDOWN:
shutDown();
// Notify the front end (if any) to shutdown.
setChanged();
notifyObservers(Event.SHUTTING_DOWN);
break;
default:
SYS_LOGGER.log(Level.WARNING, "Received unsupported control event: {0}", event.getControlEventType());
break;
}
}
}
/**
* Shuts down auto ingest.
*/
@@ -418,6 +446,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
eventPublisher.removeSubscriber(EVENT_LIST, instance);
stopInputFolderScans();
stopJobProcessing();
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.SHUTDOWN, AutoIngestManager.LOCAL_HOST_NAME));
eventPublisher.closeRemoteEventChannel();
cleanupJobs();
@@ -1695,13 +1724,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
* object.
*/
setChanged();
notifyObservers(Event.PAUSED_BY_REQUEST);
notifyObservers(Event.PAUSED_BY_USER_REQUEST);
/**
* Publish an event to let remote listeners know that the
* node has been paused.
* Publish an event to let remote listeners know that a
* pause has been requested.
*/
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_REQUEST, AutoIngestManager.LOCAL_HOST_NAME));
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_USER_REQUEST, AutoIngestManager.LOCAL_HOST_NAME));
}
}
}
@@ -1745,18 +1774,22 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
* if auto ingest is shutting down.
*/
private void pauseIfRequested() throws InterruptedException {
if (State.SHUTTING_DOWN == state) {
return;
}
synchronized (pauseLock) {
if (pauseRequested) {
SYS_LOGGER.log(Level.INFO, "Job processing paused by request");
pauseRequested = false;
setChanged();
notifyObservers(Event.PAUSED_BY_REQUEST);
notifyObservers(Event.PAUSED_BY_USER_REQUEST);
/**
* Publish an event to let remote listeners know that the
* node has been paused.
*/
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_REQUEST, AutoIngestManager.LOCAL_HOST_NAME));
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_USER_REQUEST, AutoIngestManager.LOCAL_HOST_NAME));
pauseLock.wait();
SYS_LOGGER.log(Level.INFO, "Job processing resumed after pause request");
@@ -1780,6 +1813,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
* if auto ingest is shutting down.
*/
private void pauseForSystemError() throws InterruptedException {
if (State.SHUTTING_DOWN == state) {
return;
}
synchronized (pauseLock) {
SYS_LOGGER.log(Level.SEVERE, "Job processing paused for system error");
setChanged();
@@ -3067,12 +3104,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
JOB_COMPLETED,
CASE_PRIORITIZED,
CASE_DELETED,
PAUSED_BY_REQUEST,
PAUSED_BY_USER_REQUEST,
PAUSED_FOR_SYSTEM_ERROR,
RESUMED,
STARTING_UP,
RUNNING,
SHUTTING_DOWN,
SHUTDOWN,
REPORT_STATE
}

View File

@@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.events.AutopsyEventException;
import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType;
/**
* An auto ingest monitor responsible for monitoring and reporting the
* processing of auto ingest jobs.
@@ -61,10 +62,11 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
AutoIngestManager.Event.CASE_PRIORITIZED.toString(),
AutoIngestManager.Event.JOB_STARTED.toString(),
AutoIngestManager.Event.RUNNING.toString(),
AutoIngestManager.Event.PAUSED_BY_REQUEST.toString(),
AutoIngestManager.Event.PAUSED_BY_USER_REQUEST.toString(),
AutoIngestManager.Event.PAUSED_FOR_SYSTEM_ERROR.toString(),
AutoIngestManager.Event.STARTING_UP.toString(),
AutoIngestManager.Event.SHUTTING_DOWN.toString(),
AutoIngestManager.Event.SHUTDOWN.toString(),
AutoIngestManager.Event.RESUMED.toString()}));
private final AutopsyEventPublisher eventPublisher;
private CoordinationService coordinationService;
@@ -221,9 +223,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @param event A node state change event.
*/
private void handleAutoIngestNodeStateEvent(AutoIngestNodeStateEvent event) {
if (event.getEventType() == AutoIngestManager.Event.SHUTTING_DOWN) {
AutoIngestNodeState oldNodeState = null;
if (event.getEventType() == AutoIngestManager.Event.SHUTDOWN) {
// Remove node from collection.
nodeStates.remove(event.getNodeName());
oldNodeState = nodeStates.remove(event.getNodeName());
} else {
// Otherwise either create an entry for the given node name or update
// an existing entry in the map.
@@ -231,7 +234,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
}
setChanged();
// Trigger a dashboard refresh.
notifyObservers(nodeStates.get(event.getNodeName()));
notifyObservers(oldNodeState == null ? nodeStates.get(event.getNodeName()) : oldNodeState);
}
/**
@@ -522,6 +525,45 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
}
}
/**
* Send the given control event to the given node.
*
* @param eventType The type of control event to send.
* @param nodeName The name of the node to send it to.
*/
private void sendControlEventToNode(ControlEventType eventType, String nodeName) {
new Thread(() -> {
eventPublisher.publishRemotely(new AutoIngestNodeControlEvent(eventType, nodeName));
}).start();
}
/**
* Tell the specified node to pause.
*
* @param nodeName
*/
void pauseAutoIngestNode(String nodeName) {
sendControlEventToNode(ControlEventType.PAUSE, nodeName);
}
/**
* Tell the specified node to resume.
*
* @param nodeName
*/
void resumeAutoIngestNode(String nodeName) {
sendControlEventToNode(ControlEventType.RESUME, nodeName);
}
/**
* Tell the specified node to shutdown.
*
* @param nodeName
*/
void shutdownAutoIngestNode(String nodeName) {
sendControlEventToNode(ControlEventType.SHUTDOWN, nodeName);
}
/**
* A task that queries the coordination service for auto ingest manifest
* node data and converts it to auto ingest jobs for publication top its
@@ -697,7 +739,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
case RUNNING:
nodeState = State.RUNNING;
break;
case PAUSED_BY_REQUEST:
case PAUSED_BY_USER_REQUEST:
nodeState = State.PAUSED_BY_REQUEST;
break;
case PAUSED_FOR_SYSTEM_ERROR:

View File

@@ -0,0 +1,55 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import java.io.Serializable;
import org.sleuthkit.autopsy.events.AutopsyEvent;
/**
* Event published to pause, resume or shutdown an AIN.
*/
final class AutoIngestNodeControlEvent extends AutopsyEvent implements Serializable {
/**
* The set of available controls.
*/
enum ControlEventType {
PAUSE,
RESUME,
SHUTDOWN
}
private static final long serialVersionUID = 1L;
private final String nodeName;
private final ControlEventType eventType;
AutoIngestNodeControlEvent(ControlEventType eventType, String nodeName) {
super(eventType.toString(), null, null);
this.eventType = eventType;
this.nodeName = nodeName;
}
String getNodeName() {
return nodeName;
}
ControlEventType getControlEventType() {
return eventType;
}
}