Autopsy  4.19.1
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.zookeeper.CreateMode;
37 import org.apache.zookeeper.KeeperException;
38 import org.apache.zookeeper.KeeperException.NoNodeException;
39 import org.apache.zookeeper.ZooDefs;
40 import org.openide.util.Lookup;
43 
49 @ThreadSafe
50 public final class CoordinationService {
51 
52  private static final int SESSION_TIMEOUT_MILLISECONDS = 300000;
53  private static final int CONNECTION_TIMEOUT_MILLISECONDS = 300000;
54  private static final int PORT_OFFSET = 1000; // When run in Solr, ZooKeeper defaults to Solr port + 1000
55  private static final String DEFAULT_NAMESPACE_ROOT = "autopsy";
56  @GuardedBy("CoordinationService.class")
57  private static CoordinationService instance;
58  private final CuratorFramework curator;
59  @GuardedBy("categoryNodeToPath")
60  private final Map<String, String> categoryNodeToPath;
61 
71  public synchronized static CoordinationService getInstance() throws CoordinationServiceException {
72  if (null == instance) {
73  String rootNode;
74  Collection<? extends CoordinationServiceNamespace> providers = Lookup.getDefault().lookupAll(CoordinationServiceNamespace.class);
75  Iterator<? extends CoordinationServiceNamespace> it = providers.iterator();
76  if (it.hasNext()) {
77  rootNode = it.next().getNamespaceRoot();
78  } else {
79  rootNode = DEFAULT_NAMESPACE_ROOT;
80  }
81  try {
82  instance = new CoordinationService(rootNode);
83  } catch (IOException | KeeperException | CoordinationServiceException ex) {
84  throw new CoordinationServiceException("Failed to create coordination service", ex);
85  } catch (InterruptedException ex) {
86  /*
87  * The interrupted exception should be propagated to support
88  * task cancellation. To avoid a public API change here, restore
89  * the interrupted flag and then throw the InterruptedException
90  * in its wrapper.
91  */
92  Thread.currentThread().interrupt();
93  throw new CoordinationServiceException("Failed to create coordination service", ex);
94  }
95  }
96  return instance;
97  }
98 
108  private CoordinationService(String rootNodeName) throws InterruptedException, IOException, KeeperException, CoordinationServiceException {
109 
110  // read ZK connection info
111  String hostName = UserPreferences.getZkServerHost();
112  String port = UserPreferences.getZkServerPort();
113  if (hostName.isEmpty() || port.isEmpty()) {
114  // use defaults for embedded ZK that runs on Solr server
116  int portInt = Integer.valueOf(UserPreferences.getIndexingServerPort()) + PORT_OFFSET;
117  port = Integer.toString(portInt);
118  }
119  if (false == CoordinationServiceUtils.isZooKeeperAccessible(hostName, port)) {
120  throw new CoordinationServiceException("Unable to access ZooKeeper");
121  }
122 
123  // We are using ZK for all coordination/locking, so ZK connection info cannot be changed.
124  // A reboot is required in order to use a different ZK server for coordination services.
125  /*
126  * Connect to ZooKeeper via Curator.
127  */
128  RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
129  String connectString = hostName + ":" + port;
130  curator = CuratorFrameworkFactory.newClient(connectString, SESSION_TIMEOUT_MILLISECONDS, CONNECTION_TIMEOUT_MILLISECONDS, retryPolicy);
131  curator.start();
132 
133  /*
134  * Create the top-level root and category nodes.
135  */
136  String rootNode = rootNodeName;
137 
138  if (!rootNode.startsWith("/")) {
139  rootNode = "/" + rootNode;
140  }
141  categoryNodeToPath = new ConcurrentHashMap<>();
142  for (CategoryNode node : CategoryNode.values()) {
143  String nodePath = rootNode + "/" + node.getDisplayName();
144  try {
145  curator.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(nodePath);
146  } catch (KeeperException ex) {
147  if (ex.code() != KeeperException.Code.NODEEXISTS) {
148  throw ex;
149  }
150  } catch (Exception ex) {
151  throw new CoordinationServiceException("Curator experienced an error", ex);
152  }
153  categoryNodeToPath.put(node.getDisplayName(), nodePath);
154  }
155  }
156 
177  public Lock tryGetExclusiveLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit) throws CoordinationServiceException, InterruptedException {
178  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
179  try {
180  InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath);
181  if (lock.writeLock().acquire(timeOut, timeUnit)) {
182  return new Lock(nodePath, lock.writeLock());
183  } else {
184  return null;
185  }
186  } catch (Exception ex) {
187  if (ex instanceof InterruptedException) {
188  throw (InterruptedException) ex;
189  } else {
190  throw new CoordinationServiceException(String.format("Failed to get exclusive lock for %s", fullNodePath), ex);
191  }
192  }
193  }
194 
211  public Lock tryGetExclusiveLock(CategoryNode category, String nodePath) throws CoordinationServiceException {
212  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
213  try {
214  InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath);
215  if (!lock.writeLock().acquire(0, TimeUnit.SECONDS)) {
216  return null;
217  }
218  return new Lock(nodePath, lock.writeLock());
219  } catch (Exception ex) {
220  throw new CoordinationServiceException(String.format("Failed to get exclusive lock for %s", fullNodePath), ex);
221  }
222  }
223 
244  public Lock tryGetSharedLock(CategoryNode category, String nodePath, int timeOut, TimeUnit timeUnit) throws CoordinationServiceException, InterruptedException {
245  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
246  try {
247  InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath);
248  if (lock.readLock().acquire(timeOut, timeUnit)) {
249  return new Lock(nodePath, lock.readLock());
250  } else {
251  return null;
252  }
253  } catch (Exception ex) {
254  if (ex instanceof InterruptedException) {
255  throw (InterruptedException) ex;
256  } else {
257  throw new CoordinationServiceException(String.format("Failed to get shared lock for %s", fullNodePath), ex);
258  }
259  }
260  }
261 
278  public Lock tryGetSharedLock(CategoryNode category, String nodePath) throws CoordinationServiceException {
279  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
280  try {
281  InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath);
282  if (!lock.readLock().acquire(0, TimeUnit.SECONDS)) {
283  return null;
284  }
285  return new Lock(nodePath, lock.readLock());
286  } catch (Exception ex) {
287  throw new CoordinationServiceException(String.format("Failed to get shared lock for %s", fullNodePath), ex);
288  }
289  }
290 
305  public byte[] getNodeData(CategoryNode category, String nodePath) throws CoordinationServiceException, InterruptedException {
306  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
307  try {
308  return curator.getData().forPath(fullNodePath);
309  } catch (NoNodeException ex) {
310  return null;
311  } catch (Exception ex) {
312  if (ex instanceof InterruptedException) {
313  throw (InterruptedException) ex;
314  } else {
315  throw new CoordinationServiceException(String.format("Failed to get data for %s", fullNodePath), ex);
316  }
317  }
318  }
319 
332  public void setNodeData(CategoryNode category, String nodePath, byte[] data) throws CoordinationServiceException, InterruptedException {
333  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
334  try {
335  curator.setData().forPath(fullNodePath, data);
336  } catch (Exception ex) {
337  if (ex instanceof InterruptedException) {
338  throw (InterruptedException) ex;
339  } else {
340  throw new CoordinationServiceException(String.format("Failed to set data for %s", fullNodePath), ex);
341  }
342  }
343  }
344 
357  public void deleteNode(CategoryNode category, String nodePath) throws CoordinationServiceException, InterruptedException {
358  String fullNodePath = getFullyQualifiedNodePath(category, nodePath);
359  try {
360  curator.delete().forPath(fullNodePath);
361  } catch (Exception ex) {
362  if (ex instanceof InterruptedException) {
363  throw (InterruptedException) ex;
364  } else {
365  throw new CoordinationServiceException(String.format("Failed to delete node %s", fullNodePath), ex);
366  }
367  }
368  }
369 
383  public List<String> getNodeList(CategoryNode category) throws CoordinationServiceException, InterruptedException {
384  try {
385  List<String> list = curator.getChildren().forPath(categoryNodeToPath.get(category.getDisplayName()));
386  return list;
387  } catch (Exception ex) {
388  if (ex instanceof InterruptedException) {
389  throw (InterruptedException) ex;
390  } else {
391  throw new CoordinationServiceException(String.format("Failed to get node list for %s", category.getDisplayName()), ex);
392  }
393  }
394  }
395 
404  private String getFullyQualifiedNodePath(CategoryNode category, String nodePath) {
405  // nodePath on Unix systems starts with a "/" and ZooKeeper doesn't like two slashes in a row
406  if (nodePath.startsWith("/")) {
407  return categoryNodeToPath.get(category.getDisplayName()) + nodePath.toUpperCase();
408  } else {
409  return categoryNodeToPath.get(category.getDisplayName()) + "/" + nodePath.toUpperCase();
410  }
411  }
412 
416  public final static class CoordinationServiceException extends Exception {
417 
418  private static final long serialVersionUID = 1L;
419 
420  private CoordinationServiceException(String message) {
421  super(message);
422  }
423 
424  private CoordinationServiceException(String message, Throwable cause) {
425  super(message, cause);
426  }
427  }
428 
434  public static class Lock implements AutoCloseable {
435 
440  private final InterProcessMutex interProcessLock;
441  private final String nodePath;
442 
443  private Lock(String nodePath, InterProcessMutex lock) {
444  this.nodePath = nodePath;
445  this.interProcessLock = lock;
446  }
447 
448  public String getNodePath() {
449  return nodePath;
450  }
451 
452  public void release() throws CoordinationServiceException {
453  try {
454  this.interProcessLock.release();
455  } catch (Exception ex) {
456  throw new CoordinationServiceException(String.format("Failed to release the lock on %s", nodePath), ex);
457  }
458  }
459 
460  @Override
461  public void close() throws CoordinationServiceException {
462  release();
463  }
464  }
465 
470  public enum CategoryNode {
471 
472  CASES("cases"),
473  MANIFESTS("manifests"),
474  CONFIG("config"),
475  CENTRAL_REPO("centralRepository"),
476  HEALTH_MONITOR("healthMonitor");
477 
478  private final String displayName;
479 
480  private CategoryNode(String displayName) {
481  this.displayName = displayName;
482  }
483 
484  public String getDisplayName() {
485  return displayName;
486  }
487  }
488 }
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-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.