From b52d15e437e719b1734cc697ef008fe6f365961b Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 5 Apr 2019 19:22:38 -0400 Subject: [PATCH] Initial commit of case znodes cleanup admin action --- .../autoingest/CaseNodesCleanupAction.java | 57 +++++++++ .../autoingest/CaseNodesCleanupTask.java | 120 ++++++++++++++++++ .../autoingest/DeleteCaseUtils.java | 55 ++++++++ 3 files changed, 232 insertions(+) create mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseNodesCleanupAction.java create mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseNodesCleanupTask.java create mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseUtils.java diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseNodesCleanupAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseNodesCleanupAction.java new file mode 100755 index 0000000000..2763ba2138 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseNodesCleanupAction.java @@ -0,0 +1,57 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.awt.event.ActionEvent; +import java.util.concurrent.FutureTask; +import javax.swing.AbstractAction; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.progress.AppFrameProgressBar; +import org.sleuthkit.autopsy.progress.TaskCancellable; + +/** + * An action class that kicks off a cancellable case nodes cleanup task that + * runs in a background thread and reports progress using an application frame + * progress bar. + */ +final class CaseNodesCleanupAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + @Override + @NbBundle.Messages({ + "CaseNodesCleanupAction.progressDisplayName=Cleanup Case Znodes" + }) + public void actionPerformed(ActionEvent event) { + final AppFrameProgressBar progress = new AppFrameProgressBar(Bundle.CaseNodesCleanupAction_progressDisplayName()); + final TaskCancellable taskCanceller = new TaskCancellable(progress); + progress.setCancellationBehavior(taskCanceller); + final Runnable task = new CaseNodesCleanupTask(progress); + final FutureTask future = new FutureTask<>(task, null); + taskCanceller.setFuture(future); + new Thread(future).start(); + } + + @Override + public CaseNodesCleanupAction clone() throws CloneNotSupportedException { + super.clone(); + throw new CloneNotSupportedException(); + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseNodesCleanupTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseNodesCleanupTask.java new file mode 100755 index 0000000000..7cdb29711d --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/CaseNodesCleanupTask.java @@ -0,0 +1,120 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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.File; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; +import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeDataCollector; +import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.progress.ProgressIndicator; + +/** + * Task for cleaning up case coordination service nodes for which there is no + * longer a corresponding case. + */ +final class CaseNodesCleanupTask implements Runnable { + + private static final Logger logger = AutoIngestDashboardLogger.getLogger(); + private final ProgressIndicator progress; + + /** + * Constucts an instance of a task for cleaning up case coordination service + * nodes for which there is no longer a corresponding case. + * + * @param progress + */ + CaseNodesCleanupTask(ProgressIndicator progress) { + this.progress = progress; + } + + @Override + public void run() { + CoordinationService coordinationService; + try { + coordinationService = CoordinationService.getInstance(); + } catch (CoordinationService.CoordinationServiceException ex) { + logger.log(Level.WARNING, "Error connecting to the coordination service", ex); // NON-NLS + return; + } + + List nodeDataList; + try { + nodeDataList = CaseNodeDataCollector.getNodeData(); + } catch (CoordinationService.CoordinationServiceException ex) { + logger.log(Level.WARNING, "Error collecting case node data", ex); // NON-NLS + return; + } catch (InterruptedException ex) { + logger.log(Level.WARNING, "Unexpected interrupt while collecting case node data", ex); // NON-NLS + return; + } + + for (CaseNodeData nodeData : nodeDataList) { + final Path caseDirectoryPath = nodeData.getDirectory(); + final File caseDirectory = caseDirectoryPath.toFile(); + if (!caseDirectory.exists()) { + String caseName = nodeData.getDisplayName(); + String nodePath = ""; // NON-NLS + try { + nodePath = CoordinationServiceUtils.getCaseNameNodePath(caseDirectoryPath); + deleteNode(coordinationService, caseName, nodePath); + + nodePath = CoordinationServiceUtils.getCaseResourcesNodePath(caseDirectoryPath); + deleteNode(coordinationService, caseName, nodePath); + + nodePath = CoordinationServiceUtils.getCaseAutoIngestLogNodePath(caseDirectoryPath); + deleteNode(coordinationService, caseName, nodePath); + + nodePath = CoordinationServiceUtils.getCaseDirectoryNodePath(caseDirectoryPath); + deleteNode(coordinationService, caseName, nodePath); + + } catch (InterruptedException ex) { + logger.log(Level.WARNING, String.format("Unexpected interrupt while deleting znode %s for %s", nodePath, caseName), ex); // NON-NLS + return; + } + } + } + } + + /** + * Attempts to delete a case coordination service node. + * + * @param coordinationService The ccordination service. + * @param caseName The case name. + * @param nodePath The path of the node to delete. + * + * @throws InterruptedException If the thread executing this task is + * interrupted during the delete operation. + */ + private static void deleteNode(CoordinationService coordinationService, String caseName, String nodePath) throws InterruptedException { + try { + coordinationService.deleteNode(CoordinationService.CategoryNode.CASES, nodePath); + } catch (CoordinationService.CoordinationServiceException ex) { + if (!DeleteCaseUtils.isNoNodeException(ex)) { + logger.log(Level.SEVERE, String.format("Error deleting %s znode for %s", nodePath, caseName), ex); // NON-NLS + } + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseUtils.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseUtils.java new file mode 100755 index 0000000000..848bc0a80d --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/DeleteCaseUtils.java @@ -0,0 +1,55 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2019-2019 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 org.sleuthkit.autopsy.coordinationservice.CoordinationService; + +/** + * A utility class supplying helper methods for case deletion. + */ +final class DeleteCaseUtils { + + private static final String NO_NODE_ERROR_MSG_FRAGMENT = "KeeperErrorCode = NoNode"; + + /** + * Examines a coordination service exception to try to determine if it is a + * no node exception. + * + * @param ex A coordination service exception. + * + * @return True or false. + */ + static boolean isNoNodeException(CoordinationService.CoordinationServiceException ex) { + boolean isNodeNodeEx = false; + Throwable cause = ex.getCause(); + if (cause != null) { + String causeMessage = cause.getMessage(); + isNodeNodeEx = causeMessage.contains(NO_NODE_ERROR_MSG_FRAGMENT); + } + return isNodeNodeEx; + } + + /** + * A private constructor to prevent instantiation. + */ + private DeleteCaseUtils() { + + } + +}