diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java index b68553039d..9a80706699 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboard.java @@ -52,6 +52,14 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer { autoIngestMonitor.addObserver(this); } + AutoIngestMonitor getMonitor() { + return autoIngestMonitor; + } + + AinStatusPanel getNodesStatusPanel() { + return nodesPanel; + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -143,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(); }); + } } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java index 1ab19b4a77..6ea478699e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusDashboardTopComponent.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.Component; import java.util.List; import java.util.logging.Level; import java.util.stream.Collectors; @@ -74,8 +75,6 @@ final class AinStatusDashboardTopComponent extends TopComponent { tc.add(nodeTab); tc.open(); } - tc.toFront(); - tc.requestActive(); } } @@ -115,6 +114,19 @@ final class AinStatusDashboardTopComponent extends TopComponent { WindowManager.getDefault().setTopComponentFloating(this, true); } + /** + * Get the current AutoIngestDashboard if there is one. + * + * @return the current AutoIngestDashboard or null if there is not one + */ + AinStatusDashboard getAinStatusDashboard() { + for (Component comp : getComponents()) { + if (comp instanceof AinStatusDashboard) { + return (AinStatusDashboard) comp; + } + } + return null; + } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java index 444a6fce8b..ff4cd3c478 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusNode.java @@ -97,6 +97,7 @@ final class AinStatusNode extends AbstractNode { @Messages({"AinStatusNode.hostName.title=Host Name", "AinStatusNode.status.title=Status", "AinStatusNode.status.running=Running", + "AinStatusNode.status.pauseRequested=Pause Requested", "AinStatusNode.status.pausedByUser=Paused By User", "AinStatusNode.status.pausedForError=Paused Due to System Error", "AinStatusNode.status.startingup=Starting Up", @@ -129,6 +130,9 @@ final class AinStatusNode extends AbstractNode { case PAUSED_DUE_TO_SYSTEM_ERROR: status = Bundle.AinStatusNode_status_pausedForError(); break; + case PAUSE_REQUESTED: + status = Bundle.AinStatusNode_status_pauseRequested(); + break; default: break; } @@ -142,12 +146,11 @@ final class AinStatusNode extends AbstractNode { List 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()]); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java index 095badff9a..8516151027 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AinStatusPanel.java @@ -99,12 +99,12 @@ final class AinStatusPanel extends javax.swing.JPanel implements ExplorerManager void refresh(AutoIngestMonitor monitor) { outline.setRowSelectionAllowed(false); Node[] selectedNodes = explorerManager.getSelectedNodes(); - AinStatusNode autoIngestNode = new AinStatusNode(monitor); - explorerManager.setRootContext(autoIngestNode); + AinStatusNode ainStatusNode = new AinStatusNode(monitor); + explorerManager.setRootContext(ainStatusNode); outline.setRowSelectionAllowed(true); - if (selectedNodes.length > 0 && autoIngestNode.getChildren().findChild(selectedNodes[0].getName()) != null && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored + if (selectedNodes.length > 0 && ainStatusNode.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())}); + explorerManager.setSelectedNodes(new Node[]{ainStatusNode.getChildren().findChild(selectedNodes[0].getName())}); } catch (PropertyVetoException ignore) { //Unable to select previously selected node } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java index 1a5f2657b6..617cf18e3a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -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. - } - } - } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index d097882053..a71107d197 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -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; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 843089f948..3c777f5bc0 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -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) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index fd64ff0b51..4d19d3318d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -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,14 +1724,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen * object. */ setChanged(); - notifyObservers(Event.PAUSED_BY_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)); + notifyObservers(Event.PAUSED_BY_USER_REQUEST); } + /** + * Publish an event to let remote listeners know that a pause + * has been requested. + */ + eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSE_REQUESTED, AutoIngestManager.LOCAL_HOST_NAME)); } } @@ -1745,18 +1773,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 +1812,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(); @@ -3061,12 +3097,14 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen JOB_COMPLETED, CASE_PRIORITIZED, CASE_DELETED, - PAUSED_BY_REQUEST, + PAUSE_REQUESTED, + PAUSED_BY_USER_REQUEST, PAUSED_FOR_SYSTEM_ERROR, RESUMED, STARTING_UP, RUNNING, SHUTTING_DOWN, + SHUTDOWN, REPORT_STATE } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java index 229128dae1..87adb717b7 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestMonitor.java @@ -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,12 @@ 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.PAUSE_REQUESTED.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 +224,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 +235,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 +526,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 @@ -677,6 +720,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen STARTING_UP, SHUTTING_DOWN, RUNNING, + PAUSE_REQUESTED, PAUSED_BY_REQUEST, PAUSED_DUE_TO_SYSTEM_ERROR, UNKNOWN @@ -697,7 +741,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: @@ -706,6 +750,9 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen case RESUMED: nodeState = State.RUNNING; break; + case PAUSE_REQUESTED: + nodeState = State.PAUSE_REQUESTED; + break; default: nodeState = State.UNKNOWN; break; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeControlEvent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeControlEvent.java new file mode 100644 index 0000000000..ca16a2bf8b --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNodeControlEvent.java @@ -0,0 +1,55 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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; + } +}