Autopsy  4.20.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
CoordinationService.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.coordinationservice;
20 
21 import java.io.IOException;
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.TimeUnit;
28 import javax.annotation.concurrent.GuardedBy;
29 import javax.annotation.concurrent.ThreadSafe;
30 import org.apache.curator.RetryPolicy;
31 import org.apache.curator.framework.CuratorFramework;
32 import org.apache.curator.framework.CuratorFrameworkFactory;
33 import org.apache.curator.framework.recipes.locks.InterProcessMutex;
34 import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
35 import org.apache.curator.retry.ExponentialBackoffRetry;
36 import org.apache.curator.utils.ZKPaths;
37 import org.apache.zookeeper.CreateMode;
38 import org.apache.zookeeper.KeeperException;
39 import org.apache.zookeeper.KeeperException.NoNodeException;
40 import org.apache.zookeeper.ZooDefs;
41 import org.openide.util.Lookup;
44 
50 @ThreadSafe
51 public final class CoordinationService {
52 
53  private static final int SESSION_TIMEOUT_MILLISECONDS = 300000;
54  private static final int CONNECTION_TIMEOUT_MILLISECONDS = 300000;
55  private static final int PORT_OFFSET = 1000; // When run in Solr, ZooKeeper defaults to Solr port + 1000
56  private static final String DEFAULT_NAMESPACE_ROOT = "autopsy";
57  @GuardedBy("CoordinationService.class")
58  private static CoordinationService instance;
59  private final CuratorFramework curator;
60  @GuardedBy("categoryNodeToPath")
61  private final Map<String, String> categoryNodeToPath;
62 
72  public synchronized static CoordinationService getInstance() throws CoordinationServiceException {
73  if (null == instance) {
74  String rootNode;
75  Collection<? extends CoordinationServiceNamespace> providers = Lookup.getDefault().lookupAll(CoordinationServiceNamespace.class);
76  Iterator<? extends CoordinationServiceNamespace> it = providers.iterator();
77  if (it.hasNext()) {
78  rootNode = it.next().getNamespaceRoot();
79  } else {
80  rootNode = DEFAULT_NAMESPACE_ROOT;
81  }
82  try {
83  instance = new CoordinationService(rootNode);
84  } catch (IOException | KeeperException | CoordinationServiceException ex) {
85  throw new CoordinationServiceException("Failed to create coordination service", ex);
86  } catch (InterruptedException ex) {
87  /*
88  * The interrupted exception should be propagated to support
89  * task cancellation. To avoid a public API change here, restore
90  * the interrupted flag and then throw the InterruptedException
91  * in its wrapper.
92  */
93  Thread.currentThread().interrupt();
94  throw new CoordinationServiceException("Failed to create coordination service", ex);
95  }
96  }
97  return instance;
98  }
99 
109  private CoordinationService(String rootNodeName) throws InterruptedException, IOException, KeeperException, CoordinationServiceException {
110 
111  // read ZK connection info
112  String hostName = UserPreferences.getZkServerHost();
113  String port = UserPreferences.getZkServerPort();
114  if (hostName.isEmpty() || port.isEmpty()) {
115  // use defaults for embedded ZK that runs on Solr server
117  int portInt = Integer.valueOf(UserPreferences.getIndexingServerPort()) + PORT_OFFSET;
118  port = Integer.toString(portInt);
119  }
120  if (false == CoordinationServiceUtils.isZooKeeperAccessible(hostName, port)) {
121  throw new CoordinationServiceException("Unable to access ZooKeeper");
122  }
123 
124  // We are using ZK for all coordination/locking, so ZK connection info cannot be changed.
125  // A reboot is required in order to use a different ZK server for coordination services.
126  /*
127  * Connect to ZooKeeper via Curator.
128  */
129  RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
130  String connectString = hostName + ":" + port;
131  curator = CuratorFrameworkFactory.newClient(connectString, SESSION_TIMEOUT_MILLISECONDS, CONNECTION_TIMEOUT_MILLISECONDS, retryPolicy);
132  curator.start();
133 
134  /*
135  * Create the top-level root and category nodes.
136  */
137  String rootNode = rootNodeName;
138 
139  if (!rootNode.startsWith("/")) {
140  rootNode = "/" + rootNode;
141  }
142  categoryNodeToPath = new ConcurrentHashMap<>();
143  for (CategoryNode node : CategoryNode.values()) {
144  String nodePath = rootNode + "/" + node.getDisplayName();
145  try {
146  curator.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(nodePath);
147  } catch (KeeperException ex) {
148  if (ex.code() != KeeperException.Code.NODEEXISTS) {
149  throw ex;
150  }
151  } catch (Exception ex) {
152  throw new CoordinationServiceException("Curator experienced an error", ex);
153  }
154  categoryNodeToPath.put(node.getDisplayName(), nodePath);
155  }
156  }
157 
165  private String upsertNodePath(CategoryNode category, String nodePath) throws CoordinationServiceException {
166  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
167 
168  // ZKPaths.mkdirs throws an exception with trailing slash.
169  // Remove trailing slash if slash is present to prevent this issue.
170  while(fullNodePath.endsWith("/")) {
171  fullNodePath = fullNodePath.substring(0, fullNodePath.length() - 1);
172  }
173 
174  try {
175  // ensure leading path is present
176  ZKPaths.mkdirs(curator.getZookeeperClient().getZooKeeper(), fullNodePath);
177  return fullNodePath;
178  } catch (Exception ex) {
179  throw new CoordinationServiceException("An error occurred while creating node path at: " + fullNodePath, ex);
180  }
181  }
182 
203  public Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit) throws CoordinationServiceException, InterruptedException {
204  String fullNodePath = "";
205  try {
206  // ensure node is present
207  fullNodePath = upsertNodePath(category, nodePath);
208  InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath);
209  if (lock.writeLock().acquire(timeOut, timeUnit)) {
210  return new Lock(nodePath, lock.writeLock());
211  } else {
212  return null;
213  }
214  } catch (Exception ex) {
215  if (ex instanceof InterruptedException) {
216  throw (InterruptedException) ex;
217  } else {
218  throw new CoordinationServiceException(String.format("Failed to get exclusive lock for %s", fullNodePath), ex);
219  }
220  }
221  }
222 
239  public Lock tryGetExclusiveLock(CategoryNode category, String nodePath) throws CoordinationServiceException {
240  String fullNodePath = "";
241  try {
242  // ensure node is present
243  fullNodePath = upsertNodePath(category, nodePath);
244  InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath);
245  if (!lock.writeLock().acquire(0, TimeUnit.SECONDS)) {
246  return null;
247  }
248  return new Lock(nodePath, lock.writeLock());
249  } catch (Exception ex) {
250  throw new CoordinationServiceException(String.format("Failed to get exclusive lock for %s", fullNodePath), ex);
251  }
252  }
253 
274  public Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit) throws CoordinationServiceException, InterruptedException {
275  String fullNodePath = "";
276  try {
277  // ensure node is present
278  fullNodePath = upsertNodePath(category, nodePath);
279  InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath);
280  if (lock.readLock().acquire(timeOut, timeUnit)) {
281  return new Lock(nodePath, lock.readLock());
282  } else {
283  return null;
284  }
285  } catch (Exception ex) {
286  if (ex instanceof InterruptedException) {
287  throw (InterruptedException) ex;
288  } else {
289  throw new CoordinationServiceException(String.format("Failed to get shared lock for %s", fullNodePath), ex);
290  }
291  }
292  }
293 
310  public Lock tryGetSharedLock(CategoryNode category, String nodePath) throws CoordinationServiceException {
311  String fullNodePath = "";
312  try {
313  // ensure node is present
314  fullNodePath = upsertNodePath(category, nodePath);
315  InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath);
316  if (!lock.readLock().acquire(0, TimeUnit.SECONDS)) {
317  return null;
318  }
319  return new Lock(nodePath, lock.readLock());
320  } catch (Exception ex) {
321  throw new CoordinationServiceException(String.format("Failed to get shared lock for %s", fullNodePath), ex);
322  }
323  }
324 
339  public byte[] getNodeData(CategoryNode category, String nodePath) throws CoordinationServiceException, InterruptedException {
340  String fullNodePath = "";
341  try {
342  // ensure node is present
343  fullNodePath = upsertNodePath(category, nodePath);
344 
345  // return node data path
346  return curator.getData().forPath(fullNodePath);
347  } catch (NoNodeException ex) {
348  return null;
349  } catch (Exception ex) {
350  if (ex instanceof InterruptedException) {
351  throw (InterruptedException) ex;
352  } else {
353  throw new CoordinationServiceException(String.format("Failed to get data for %s", fullNodePath), ex);
354  }
355  }
356  }
357 
370  public void setNodeData(CategoryNode category, String nodePath, byte[] data) throws CoordinationServiceException, InterruptedException {
371  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
372  try {
373  curator.setData().forPath(fullNodePath, data);
374  } catch (Exception ex) {
375  if (ex instanceof InterruptedException) {
376  throw (InterruptedException) ex;
377  } else {
378  throw new CoordinationServiceException(String.format("Failed to set data for %s", fullNodePath), ex);
379  }
380  }
381  }
382 
395  public void deleteNode(CategoryNode category, String nodePath) throws CoordinationServiceException, InterruptedException {
396  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
397  try {
398  curator.delete().forPath(fullNodePath);
399  } catch (Exception ex) {
400  if (ex instanceof InterruptedException) {
401  throw (InterruptedException) ex;
402  } else {
403  throw new CoordinationServiceException(String.format("Failed to delete node %s", fullNodePath), ex);
404  }
405  }
406  }
407 
421  public List<String> getNodeList(CategoryNode category) throws CoordinationServiceException, InterruptedException {
422  try {
423  List<String> list = curator.getChildren().forPath(categoryNodeToPath.get(category.getDisplayName()));
424  return list;
425  } catch (Exception ex) {
426  if (ex instanceof InterruptedException) {
427  throw (InterruptedException) ex;
428  } else {
429  throw new CoordinationServiceException(String.format("Failed to get node list for %s", category.getDisplayName()), ex);
430  }
431  }
432  }
433 
442  private String getFullyQualifiedNodePath(CategoryNode category, String nodePath) {
443  // nodePath on Unix systems starts with a "/" and ZooKeeper doesn't like two slashes in a row
444  if (nodePath.startsWith("/")) {
445  return categoryNodeToPath.get(category.getDisplayName()) + nodePath.toUpperCase();
446  } else {
447  return categoryNodeToPath.get(category.getDisplayName()) + "/" + nodePath.toUpperCase();
448  }
449  }
450 
454  public final static class CoordinationServiceException extends Exception {
455 
456  private static final long serialVersionUID = 1L;
457 
458  private CoordinationServiceException(String message) {
459  super(message);
460  }
461 
462  private CoordinationServiceException(String message, Throwable cause) {
463  super(message, cause);
464  }
465  }
466 
472  public static class Lock implements AutoCloseable {
473 
478  private final InterProcessMutex interProcessLock;
479  private final String nodePath;
480 
481  private Lock(String nodePath, InterProcessMutex lock) {
482  this.nodePath = nodePath;
483  this.interProcessLock = lock;
484  }
485 
486  public String getNodePath() {
487  return nodePath;
488  }
489 
490  public void release() throws CoordinationServiceException {
491  try {
492  this.interProcessLock.release();
493  } catch (Exception ex) {
494  throw new CoordinationServiceException(String.format("Failed to release the lock on %s", nodePath), ex);
495  }
496  }
497 
498  @Override
499  public void close() throws CoordinationServiceException {
500  release();
501  }
502  }
503 
508  public enum CategoryNode {
509 
510  CASES("cases"),
511  MANIFESTS("manifests"),
512  CONFIG("config"),
513  CENTRAL_REPO("centralRepository"),
514  HEALTH_MONITOR("healthMonitor");
515 
516  private final String displayName;
517 
518  private CategoryNode(String displayName) {
519  this.displayName = displayName;
520  }
521 
522  public String getDisplayName() {
523  return displayName;
524  }
525  }
526 }
String upsertNodePath(CategoryNode category, String nodePath)
Lock tryGetExclusiveLock(CategoryNode category, String nodePath)
void deleteNode(CategoryNode category, String nodePath)
byte[] getNodeData(CategoryNode category, String nodePath)
String getFullyQualifiedNodePath(CategoryNode category, String nodePath)
Lock tryGetSharedLock(CategoryNode category, String nodePath)
Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)
void setNodeData(CategoryNode category, String nodePath, byte[] data)
Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.