diff --git a/ZookeeperNodeMigration/.gitignore b/ZookeeperNodeMigration/.gitignore new file mode 100755 index 0000000000..3d7c2414b1 --- /dev/null +++ b/ZookeeperNodeMigration/.gitignore @@ -0,0 +1,3 @@ +/nbproject/private/ +/build/ +/dist/ diff --git a/ZookeeperNodeMigration/build.xml b/ZookeeperNodeMigration/build.xml new file mode 100755 index 0000000000..9705ad3562 --- /dev/null +++ b/ZookeeperNodeMigration/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project ZookeeperNodeMigration. + + + diff --git a/ZookeeperNodeMigration/docs/README.TXT b/ZookeeperNodeMigration/docs/README.TXT new file mode 100755 index 0000000000..9f5b8f4ca9 --- /dev/null +++ b/ZookeeperNodeMigration/docs/README.TXT @@ -0,0 +1,23 @@ +To run the project from the command line, go to the folder that contains "ZookeeperNodeMigration.jar" and +type the following: + +java -jar ZookeeperNodeMigration.jar + +To distribute this project, zip up the dist folder (including the lib folder) +and distribute the ZIP file. + +Usage: +ZookeeperNodeMigration input needs to be: +[Input Zookeeper IP Address or Hostname] [Input Zookeeper Port Number] [Output Zookeeper IP Address or Hostname] [Output Zookeeper Port Number] + +For example, if you execute the following command from command line line, the Zookeeper +nodes will get copied from Zookeeper server on localhost:9983 to Zookeeper server on localhost:19983 : + +java -jar ZookeeperNodeMigration.jar localhost 9983 localhost 19983 + + +If you do not have Java installed on the machine, you can use the packaged version of Java that is distributed along +with Autopsy. For example: + +"C:\Program Files\Autopsy-4.16.0\jre\bin\java.exe" -jar ZookeeperNodeMigration.jar localhost 9983 localhost 19983 + diff --git a/ZookeeperNodeMigration/manifest.mf b/ZookeeperNodeMigration/manifest.mf new file mode 100755 index 0000000000..328e8e5bc3 --- /dev/null +++ b/ZookeeperNodeMigration/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/ZookeeperNodeMigration/nbproject/build-impl.xml b/ZookeeperNodeMigration/nbproject/build-impl.xml new file mode 100755 index 0000000000..76af3c9f63 --- /dev/null +++ b/ZookeeperNodeMigration/nbproject/build-impl.xml @@ -0,0 +1,1770 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ZookeeperNodeMigration/nbproject/project.properties b/ZookeeperNodeMigration/nbproject/project.properties new file mode 100755 index 0000000000..26892390a5 --- /dev/null +++ b/ZookeeperNodeMigration/nbproject/project.properties @@ -0,0 +1,107 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=ZookeeperNodeMigration +application.vendor=elivis +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.modulepath=\ + ${run.modulepath} +debug.test.classpath=\ + ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/ZookeeperNodeMigration.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +file.reference.curator-client-2.8.0.jar=release/curator-client-2.8.0.jar +file.reference.curator-framework-2.8.0.jar=release/curator-framework-2.8.0.jar +file.reference.curator-recipes-2.8.0.jar=release/curator-recipes-2.8.0.jar +file.reference.guava-17.0.jar=release/guava-17.0.jar +file.reference.log4j-1.2.17.jar=release/log4j-1.2.17.jar +file.reference.slf4j-api-1.7.24.jar=release/slf4j-api-1.7.24.jar +file.reference.slf4j-log4j12-1.7.6.jar=release/slf4j-log4j12-1.7.6.jar +file.reference.zookeeper-3.4.6.jar=release/zookeeper-3.4.6.jar +includes=** +jar.compress=false +javac.classpath=\ + ${file.reference.curator-client-2.8.0.jar}:\ + ${file.reference.curator-framework-2.8.0.jar}:\ + ${file.reference.curator-recipes-2.8.0.jar}:\ + ${file.reference.zookeeper-3.4.6.jar}:\ + ${file.reference.slf4j-api-1.7.24.jar}:\ + ${file.reference.slf4j-log4j12-1.7.6.jar}:\ + ${file.reference.log4j-1.2.17.jar}:\ + ${file.reference.guava-17.0.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=true +javac.modulepath= +javac.processormodulepath= +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.modulepath=\ + ${javac.modulepath} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.html5=false +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +jlink.launcher=false +jlink.launcher.name=ZookeeperNodeMigration +main.class=zookeepernodemigration.ZookeeperNodeMigration +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.modulepath=\ + ${javac.modulepath} +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +run.test.modulepath=\ + ${javac.test.modulepath} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/ZookeeperNodeMigration/nbproject/project.xml b/ZookeeperNodeMigration/nbproject/project.xml new file mode 100755 index 0000000000..1db04676a2 --- /dev/null +++ b/ZookeeperNodeMigration/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + ZookeeperNodeMigration + + + + + + + + + diff --git a/ZookeeperNodeMigration/release/curator-client-2.8.0.jar b/ZookeeperNodeMigration/release/curator-client-2.8.0.jar new file mode 100755 index 0000000000..4ccc265cc4 Binary files /dev/null and b/ZookeeperNodeMigration/release/curator-client-2.8.0.jar differ diff --git a/ZookeeperNodeMigration/release/curator-framework-2.8.0.jar b/ZookeeperNodeMigration/release/curator-framework-2.8.0.jar new file mode 100755 index 0000000000..5e488892d1 Binary files /dev/null and b/ZookeeperNodeMigration/release/curator-framework-2.8.0.jar differ diff --git a/ZookeeperNodeMigration/release/curator-recipes-2.8.0.jar b/ZookeeperNodeMigration/release/curator-recipes-2.8.0.jar new file mode 100755 index 0000000000..34eb9c9677 Binary files /dev/null and b/ZookeeperNodeMigration/release/curator-recipes-2.8.0.jar differ diff --git a/ZookeeperNodeMigration/release/guava-17.0.jar b/ZookeeperNodeMigration/release/guava-17.0.jar new file mode 100755 index 0000000000..661fc7473f Binary files /dev/null and b/ZookeeperNodeMigration/release/guava-17.0.jar differ diff --git a/ZookeeperNodeMigration/release/log4j-1.2.17.jar b/ZookeeperNodeMigration/release/log4j-1.2.17.jar new file mode 100755 index 0000000000..1d425cf7d7 Binary files /dev/null and b/ZookeeperNodeMigration/release/log4j-1.2.17.jar differ diff --git a/ZookeeperNodeMigration/release/slf4j-api-1.7.24.jar b/ZookeeperNodeMigration/release/slf4j-api-1.7.24.jar new file mode 100755 index 0000000000..05941a12f0 Binary files /dev/null and b/ZookeeperNodeMigration/release/slf4j-api-1.7.24.jar differ diff --git a/ZookeeperNodeMigration/release/slf4j-log4j12-1.7.6.jar b/ZookeeperNodeMigration/release/slf4j-log4j12-1.7.6.jar new file mode 100755 index 0000000000..d1cc2456e4 Binary files /dev/null and b/ZookeeperNodeMigration/release/slf4j-log4j12-1.7.6.jar differ diff --git a/ZookeeperNodeMigration/release/zookeeper-3.4.6.jar b/ZookeeperNodeMigration/release/zookeeper-3.4.6.jar new file mode 100755 index 0000000000..7c340be9f5 Binary files /dev/null and b/ZookeeperNodeMigration/release/zookeeper-3.4.6.jar differ diff --git a/ZookeeperNodeMigration/src/zookeepernodemigration/AutoIngestJobNodeData.java b/ZookeeperNodeMigration/src/zookeepernodemigration/AutoIngestJobNodeData.java new file mode 100755 index 0000000000..82e75a7b1c --- /dev/null +++ b/ZookeeperNodeMigration/src/zookeepernodemigration/AutoIngestJobNodeData.java @@ -0,0 +1,687 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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 zookeepernodemigration; + +import java.io.Serializable; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Date; +import javax.lang.model.type.TypeKind; + +/** + * An object that converts auto ingest job data for an auto ingest job + * coordination service node to and from byte arrays. + */ +final class AutoIngestJobNodeData { + + private static final int CURRENT_VERSION = 2; + private static final int DEFAULT_PRIORITY = 0; + + /* + * This number is the sum of each piece of data, based on it's type. For the + * types boolean, int, and long, values 1, 4, and 8 will be added + * respectively. For String objects, the length of the string, plus either a + * byte or short respesenting the length of the string, will be added. + * + * This field is used to set the size of the buffer during the byte array + * creation in the 'toArray()' method. Since the final size of the array + * isn't immediately known at the time of creation, this number is used to + * create an array as large as could possibly be needed to store all the + * data. This avoids the need to continuously enlarge the buffer. Once the + * buffer has all the necessary data, it will be resized as appropriate. + */ + private static final int MAX_POSSIBLE_NODE_DATA_SIZE = 131637; + + /* + * Version 0 fields. + */ + private int processingStatus; + private int priority; + private int numberOfCrashes; + private long completedDate; + private boolean errorsOccurred; + + /* + * Version 1 fields. + */ + private int version; + private String manifestFilePath; // 'short' length used in byte array + private long manifestFileDate; + private String caseName; // 'byte' length used in byte array + private String deviceId; // 'byte' length used in byte array + private String dataSourcePath; // 'short' length used in byte array + private String caseDirectoryPath; // 'short' length used in byte array + private String processingHostName; // 'short' length used in byte array + private byte processingStage; + private long processingStageStartDate; + private String processingStageDetailsDescription; // 'byte' length used in byte array + private long processingStageDetailsStartDate; + + /* + * Version 2 fields. + */ + private long dataSourceSize; + + /** + * Processing statuses for an auto ingest job. + */ + enum ProcessingStatus { + PENDING, + PROCESSING, + COMPLETED, + DELETED + } + + /** + * Processing stages for an auto ingest job. + */ + enum Stage { + + PENDING("Pending"), + STARTING("Starting"), + UPDATING_SHARED_CONFIG("Updating shared configuration"), + CHECKING_SERVICES("Checking services"), + OPENING_CASE("Opening case"), + IDENTIFYING_DATA_SOURCE("Identifying data source type"), + ADDING_DATA_SOURCE("Adding data source"), + ANALYZING_DATA_SOURCE("Analyzing data source"), + ANALYZING_FILES("Analyzing files"), + EXPORTING_FILES("Exporting files"), + CANCELLING_MODULE("Cancelling module"), + CANCELLING("Cancelling"), + COMPLETED("Completed"); + + private final String displayText; + + private Stage(String displayText) { + this.displayText = displayText; + } + + String getDisplayText() { + return displayText; + } + + } + + + /** + * Processing stage details for an auto ingest job. + */ + static final class StageDetails implements Serializable { + + private static final long serialVersionUID = 1L; + private final String description; + private final Date startDate; + + StageDetails(String description, Date startDate) { + this.description = description; + this.startDate = startDate; + } + + String getDescription() { + return this.description; + } + + Date getStartDate() { + return new Date(this.startDate.getTime()); + } + + } + + /** + * Gets the current version of the auto ingest job coordination service node + * data. + * + * @return The version number. + */ + static int getCurrentVersion() { + return AutoIngestJobNodeData.CURRENT_VERSION; + } + + /** + * Uses a coordination service node data to construct an object that + * converts auto ingest job data for an auto ingest job coordination service + * node to and from byte arrays. + * + * @param nodeData The raw bytes received from the coordination service. + */ + AutoIngestJobNodeData(byte[] nodeData) throws InvalidDataException { + if (null == nodeData || nodeData.length == 0) { + throw new InvalidDataException(null == nodeData ? "Null nodeData byte array" : "Zero-length nodeData byte array"); + } + + /* + * Set default values for all fields. + */ + this.processingStatus = ProcessingStatus.PENDING.ordinal(); + this.priority = DEFAULT_PRIORITY; + this.numberOfCrashes = 0; + this.completedDate = 0L; + this.errorsOccurred = false; + this.version = 0; + this.manifestFilePath = ""; + this.manifestFileDate = 0L; + this.caseName = ""; + this.deviceId = ""; + this.dataSourcePath = ""; + this.caseDirectoryPath = ""; + this.processingHostName = ""; + this.processingStage = (byte) Stage.PENDING.ordinal(); + this.processingStageStartDate = 0L; + this.processingStageDetailsDescription = ""; + this.processingStageDetailsStartDate = 0L; + this.dataSourceSize = 0L; + + /* + * Get fields from node data. + */ + ByteBuffer buffer = ByteBuffer.wrap(nodeData); + try { + if (buffer.hasRemaining()) { + /* + * Get version 0 fields. + */ + this.processingStatus = buffer.getInt(); + this.priority = buffer.getInt(); + this.numberOfCrashes = buffer.getInt(); + this.completedDate = buffer.getLong(); + int errorFlag = buffer.getInt(); + this.errorsOccurred = (1 == errorFlag); + } + + if (buffer.hasRemaining()) { + /* + * Get version 1 fields. + */ + this.version = buffer.getInt(); + this.deviceId = getStringFromBuffer(buffer, TypeKind.BYTE); + this.caseName = getStringFromBuffer(buffer, TypeKind.BYTE); + this.caseDirectoryPath = getStringFromBuffer(buffer, TypeKind.SHORT); + this.manifestFileDate = buffer.getLong(); + this.manifestFilePath = getStringFromBuffer(buffer, TypeKind.SHORT); + this.dataSourcePath = getStringFromBuffer(buffer, TypeKind.SHORT); + this.processingStage = buffer.get(); + this.processingStageStartDate = buffer.getLong(); + this.processingStageDetailsDescription = getStringFromBuffer(buffer, TypeKind.BYTE); + this.processingStageDetailsStartDate = buffer.getLong(); + this.processingHostName = getStringFromBuffer(buffer, TypeKind.SHORT); + } + + if (buffer.hasRemaining()) { + /* + * Get version 2 fields. + */ + this.dataSourceSize = buffer.getLong(); + } + + } catch (BufferUnderflowException ex) { + throw new InvalidDataException("Node data is incomplete", ex); + } + } + + /** + * Gets the processing status of the job. + * + * @return The processing status. + */ + ProcessingStatus getProcessingStatus() { + return ProcessingStatus.values()[this.processingStatus]; + } + + /** + * Sets the processing status of the job. + * + * @param processingSatus The processing status. + */ + void setProcessingStatus(ProcessingStatus processingStatus) { + this.processingStatus = processingStatus.ordinal(); + } + + /** + * Gets the priority of the job. + * + * @return The priority. + */ + int getPriority() { + return this.priority; + } + + /** + * Sets the priority of the job. A higher number indicates a higheer + * priority. + * + * @param priority The priority. + */ + void setPriority(int priority) { + this.priority = priority; + } + + /** + * Gets the number of times the job has crashed during processing. + * + * @return The number of crashes. + */ + int getNumberOfCrashes() { + return this.numberOfCrashes; + } + + /** + * Sets the number of times the job has crashed during processing. + * + * @param numberOfCrashes The number of crashes. + */ + void setNumberOfCrashes(int numberOfCrashes) { + this.numberOfCrashes = numberOfCrashes; + } + + /** + * Gets the date the job was completed. A completion date equal to the epoch + * (January 1, 1970, 00:00:00 GMT), i.e., Date.getTime() returns 0L, + * indicates the job has not been completed. + * + * @return The job completion date. + */ + Date getCompletedDate() { + return new Date(this.completedDate); + } + + /** + * Sets the date the job was completed. A completion date equal to the epoch + * (January 1, 1970, 00:00:00 GMT), i.e., Date.getTime() returns 0L, + * indicates the job has not been completed. + * + * @param completedDate The job completion date. + */ + void setCompletedDate(Date completedDate) { + this.completedDate = completedDate.getTime(); + } + + /** + * Gets whether or not any errors occurred during the processing of the job. + * + * @return True or false. + */ + boolean getErrorsOccurred() { + return this.errorsOccurred; + } + + /** + * Sets whether or not any errors occurred during the processing of job. + * + * @param errorsOccurred True or false. + */ + void setErrorsOccurred(boolean errorsOccurred) { + this.errorsOccurred = errorsOccurred; + } + + /** + * Gets the node data version number. + * + * @return The version number. + */ + int getVersion() { + return this.version; + } + + /** + * Gets the device ID of the device associated with the data source for the + * job. + * + * @return The device ID. + */ + String getDeviceId() { + return this.deviceId; + } + + /** + * Sets the device ID of the device associated with the data source for the + * job. + * + * @param deviceId The device ID. + */ + void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + /** + * Gets the case name. + * + * @return The case name. + */ + String getCaseName() { + return this.caseName; + } + + /** + * Sets the case name. + * + * @param caseName The case name. + */ + void setCaseName(String caseName) { + this.caseName = caseName; + } + + /** + * Sets the path to the case directory of the case associated with the job. + * + * @param caseDirectoryPath The path to the case directory. + */ + synchronized void setCaseDirectoryPath(Path caseDirectoryPath) { + if (caseDirectoryPath == null) { + this.caseDirectoryPath = ""; + } else { + this.caseDirectoryPath = caseDirectoryPath.toString(); + } + } + + /** + * Gets the path to the case directory of the case associated with the job. + * + * @return The case directory path or an empty string path if the case + * directory has not been created yet. + */ + synchronized Path getCaseDirectoryPath() { + if (!caseDirectoryPath.isEmpty()) { + return Paths.get(caseDirectoryPath); + } else { + return Paths.get(""); + } + } + + /** + * Gets the date the manifest was created. + * + * @return The date the manifest was created. + */ + Date getManifestFileDate() { + return new Date(this.manifestFileDate); + } + + /** + * Sets the date the manifest was created. + * + * @param manifestFileDate The date the manifest was created. + */ + void setManifestFileDate(Date manifestFileDate) { + this.manifestFileDate = manifestFileDate.getTime(); + } + + /** + * Gets the manifest file path. + * + * @return The manifest file path. + */ + Path getManifestFilePath() { + return Paths.get(this.manifestFilePath); + } + + /** + * Sets the manifest file path. + * + * @param manifestFilePath The manifest file path. + */ + void setManifestFilePath(Path manifestFilePath) { + if (manifestFilePath != null) { + this.manifestFilePath = manifestFilePath.toString(); + } else { + this.manifestFilePath = ""; + } + } + + /** + * Gets the path of the data source for the job. + * + * @return The data source path. + */ + Path getDataSourcePath() { + return Paths.get(dataSourcePath); + } + + /** + * Get the file name portion of the path of the data source for the job. + * + * @return The data source file name. + */ + public String getDataSourceFileName() { + return Paths.get(dataSourcePath).getFileName().toString(); + } + + /** + * Sets the path of the data source for the job. + * + * @param dataSourcePath The data source path. + */ + void setDataSourcePath(Path dataSourcePath) { + if (dataSourcePath != null) { + this.dataSourcePath = dataSourcePath.toString(); + } else { + this.dataSourcePath = ""; + } + } + + /** + * Get the processing stage of the job. + * + * @return The processing stage. + */ + Stage getProcessingStage() { + return Stage.values()[this.processingStage]; + } + + /** + * Sets the processing stage job. + * + * @param processingStage The processing stage. + */ + void setProcessingStage(Stage processingStage) { + this.processingStage = (byte) processingStage.ordinal(); + } + + /** + * Gets the processing stage start date. + * + * @return The processing stage start date. + */ + Date getProcessingStageStartDate() { + return new Date(this.processingStageStartDate); + } + + /** + * Sets the processing stage start date. + * + * @param processingStageStartDate The processing stage start date. + */ + void setProcessingStageStartDate(Date processingStageStartDate) { + this.processingStageStartDate = processingStageStartDate.getTime(); + } + + /** + * Get the processing stage details. + * + * @return A processing stage details object. + */ + StageDetails getProcessingStageDetails() { + return new StageDetails(this.processingStageDetailsDescription, new Date(this.processingStageDetailsStartDate)); + } + + /** + * Sets the details of the current processing stage. + * + * @param stageDetails A stage details object. + */ + void setProcessingStageDetails(StageDetails stageDetails) { + this.processingStageDetailsDescription = stageDetails.getDescription(); + this.processingStageDetailsStartDate = stageDetails.getStartDate().getTime(); + } + + /** + * Gets the processing host name, may be the empty string. + * + * @return The processing host. The empty string if the job is not currently + * being processed. + */ + String getProcessingHostName() { + return this.processingHostName; + } + + /** + * Sets the processing host name. May be the empty string. + * + * @param processingHost The processing host name. The empty string if the + * job is not currently being processed. + */ + void setProcessingHostName(String processingHost) { + this.processingHostName = processingHost; + } + + /** + * Gets the total size of the data source. + * + * @return The data source size. + */ + long getDataSourceSize() { + return this.dataSourceSize; + } + + /** + * Sets the total size of the data source. + * + * @param dataSourceSize The data source size. + */ + void setDataSourceSize(long dataSourceSize) { + this.dataSourceSize = dataSourceSize; + } + + /** + * Gets the node data as a byte array that can be sent to the coordination + * service. + * + * @return The node data as a byte array. + */ + byte[] toArray() { + ByteBuffer buffer = ByteBuffer.allocate(MAX_POSSIBLE_NODE_DATA_SIZE); + + // Write data (compatible with version 0) + buffer.putInt(this.processingStatus); + buffer.putInt(this.priority); + buffer.putInt(this.numberOfCrashes); + buffer.putLong(this.completedDate); + buffer.putInt(this.errorsOccurred ? 1 : 0); + + if (this.version >= 1) { + // Write version + buffer.putInt(this.version); + + // Write data + putStringIntoBuffer(deviceId, buffer, TypeKind.BYTE); + putStringIntoBuffer(caseName, buffer, TypeKind.BYTE); + putStringIntoBuffer(caseDirectoryPath, buffer, TypeKind.SHORT); + buffer.putLong(this.manifestFileDate); + putStringIntoBuffer(manifestFilePath, buffer, TypeKind.SHORT); + putStringIntoBuffer(dataSourcePath, buffer, TypeKind.SHORT); + buffer.put(this.processingStage); + buffer.putLong(this.processingStageStartDate); + putStringIntoBuffer(this.processingStageDetailsDescription, buffer, TypeKind.BYTE); + buffer.putLong(this.processingStageDetailsStartDate); + putStringIntoBuffer(processingHostName, buffer, TypeKind.SHORT); + + if (this.version >= 2) { + buffer.putLong(this.dataSourceSize); + } + } + + // Prepare the array + byte[] array = new byte[buffer.position()]; + buffer.rewind(); + buffer.get(array, 0, array.length); + + return array; + } + + /** + * This method retrieves a string from a given buffer. Depending on the type + * specified, either a 'byte' or a 'short' will first be read out of the + * buffer which gives the length of the string so it can be properly parsed. + * + * @param buffer The buffer from which the string will be read. + * @param lengthType The size of the length data. + * + * @return The string read from the buffer. + */ + private String getStringFromBuffer(ByteBuffer buffer, TypeKind lengthType) { + int length = 0; + String output = ""; + + switch (lengthType) { + case BYTE: + length = buffer.get(); + break; + case SHORT: + length = buffer.getShort(); + break; + } + + if (length > 0) { + byte[] array = new byte[length]; + buffer.get(array, 0, length); + output = new String(array); + } + + return output; + } + + /** + * This method puts a given string into a given buffer. Depending on the + * type specified, either a 'byte' or a 'short' will be inserted prior to + * the string which gives the length of the string so it can be properly + * parsed. + * + * @param stringValue The string to write to the buffer. + * @param buffer The buffer to which the string will be written. + * @param lengthType The size of the length data. + */ + private void putStringIntoBuffer(String stringValue, ByteBuffer buffer, TypeKind lengthType) { + switch (lengthType) { + case BYTE: + buffer.put((byte) stringValue.length()); + break; + case SHORT: + buffer.putShort((short) stringValue.length()); + break; + } + + buffer.put(stringValue.getBytes()); + } + + final static class InvalidDataException extends Exception { + + private static final long serialVersionUID = 1L; + + private InvalidDataException(String message) { + super(message); + } + + private InvalidDataException(String message, Throwable cause) { + super(message, cause); + } + } + +} diff --git a/ZookeeperNodeMigration/src/zookeepernodemigration/TimeStampUtils.java b/ZookeeperNodeMigration/src/zookeepernodemigration/TimeStampUtils.java new file mode 100755 index 0000000000..c04feca3c1 --- /dev/null +++ b/ZookeeperNodeMigration/src/zookeepernodemigration/TimeStampUtils.java @@ -0,0 +1,93 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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 zookeepernodemigration; + +import java.text.SimpleDateFormat; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility methods for working with time stamps of the form + * 'yyyy_MM_dd_HH_mm_ss'. + */ +final class TimeStampUtils { + + /* + * Sample time stamp suffix: 2015_02_02_12_10_31 + */ + private static final Pattern TIME_STAMP_PATTERN = Pattern.compile("\\d{4}_\\d{2}_\\d{2}_\\d{2}_\\d{2}_\\d{2}$"); + private static final int LENGTH_OF_DATE_TIME_STAMP = 20; // length of the above time stamp + + /** + * Checks whether a string ends with a time stamp. + * + * @param inputString The string to check. + * + * @return True or false. + */ + static boolean endsWithTimeStamp(String inputString) { + Matcher m = TIME_STAMP_PATTERN.matcher(inputString); + return m.find(); + } + + /** + * Gets the fixed length of the time stamp suffix. + * + * @return The length. + */ + static int getTimeStampLength() { + return LENGTH_OF_DATE_TIME_STAMP; + } + + /** + * Removes the time stamp suffix from a string, if present. + * + * @param inputString The string to trim. + * + * @return The trimmed string. + */ + static String removeTimeStamp(String inputString) { + String trimmedString = inputString; + if (inputString != null && endsWithTimeStamp(inputString)) { + trimmedString = inputString.substring(0, inputString.length() - getTimeStampLength()); + } + return trimmedString; + } + + /** + * Gets the time stamp suffix from a string, if present. + * + * @param inputString the name to check for a timestamp + * + * @return The time stamp, may be the empty. + */ + static String getTimeStampOnly(String inputString) { + String timeStamp = ""; + if (inputString != null && endsWithTimeStamp(inputString)) { + timeStamp = inputString.substring(inputString.length() - getTimeStampLength(), inputString.length()); + } + return timeStamp; + } + + /* + * Private contructor to prevent instantiation. + */ + private TimeStampUtils() { + } +} diff --git a/ZookeeperNodeMigration/src/zookeepernodemigration/ZookeeperNodeMigration.java b/ZookeeperNodeMigration/src/zookeepernodemigration/ZookeeperNodeMigration.java new file mode 100755 index 0000000000..c62db26b84 --- /dev/null +++ b/ZookeeperNodeMigration/src/zookeepernodemigration/ZookeeperNodeMigration.java @@ -0,0 +1,464 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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 zookeepernodemigration; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.recipes.locks.InterProcessMutex; +import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.NoNodeException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; + +/** + * Utility to migrate Autopsy coordination service data from one ZK database to + * another. + */ +public class ZookeeperNodeMigration { + + private static final Logger LOGGER = Logger.getLogger(ZookeeperNodeMigration.class.getName()); + private static final int SESSION_TIMEOUT_MILLISECONDS = 300000; + private static final int CONNECTION_TIMEOUT_MILLISECONDS = 300000; + private static final int ZOOKEEPER_SESSION_TIMEOUT_MILLIS = 3000; + private static final int ZOOKEEPER_CONNECTION_TIMEOUT_MILLIS = 15000; + private static final String DEFAULT_NAMESPACE_ROOT = "autopsy"; + private static CuratorFramework inputCurator; + private static CuratorFramework outputCurator; + private static Map categoryNodeToPath; + + private ZookeeperNodeMigration(){ + } + + /** + * Main method. + * + * @param args the command line arguments + */ + public static void main(String[] args) { + + String inputZkIpAddr, inputZkPort, outputZkIpAddr, outputZkPort; + + if (args.length == 4) { + inputZkIpAddr = args[0]; + inputZkPort = args[1]; + outputZkIpAddr = args[2]; + outputZkPort = args[3]; + } else { + System.out.println("Input needs to be [Input Zookeeper IP Address] [Input Zookeeper Port Number] [Output Zookeeper IP Address] [Output Zookeeper Port Number]"); + LOGGER.log(Level.SEVERE, "Input needs to be [Input Zookeeper IP Address] [Input Zookeeper Port Number] [Output Zookeeper IP Address] [Output Zookeeper Port Number]"); + return; + } + + if (inputZkIpAddr.isEmpty() || inputZkPort.isEmpty() || outputZkIpAddr.isEmpty() || outputZkPort.isEmpty()) { + System.out.println("Input needs to be [Input Zookeeper IP Address] [Input Zookeeper Port Number] [Output Zookeeper IP Address] [Output Zookeeper Port Number]"); + LOGGER.log(Level.SEVERE, "Input needs to be [Input Zookeeper IP Address] [Input Zookeeper Port Number] [Output Zookeeper IP Address] [Output Zookeeper Port Number]"); + return; + } + + inputCurator = initializeCurator(inputZkIpAddr, inputZkPort); + if (inputCurator == null) { + System.out.println("Unable to initialize Zookeeper or Curator: " + inputZkIpAddr + ":" + inputZkPort); + LOGGER.log(Level.SEVERE, "Unable to initialize Zookeeper or Curator: {0}:{1}", new Object[]{inputZkIpAddr, inputZkPort}); + return; + } + + try { + categoryNodeToPath = populateCategoryNodes(inputCurator); + } catch (KeeperException | CoordinationServiceException ex) { + System.out.println("Unable to initialize Curator: " + inputZkIpAddr + ":" + inputZkPort); + LOGGER.log(Level.SEVERE, "Unable to initialize Curator: {0}:{1}", new Object[]{inputZkIpAddr, inputZkPort}); + return; + } + + outputCurator = initializeCurator(outputZkIpAddr, outputZkPort); + if (outputCurator == null) { + System.out.println("Unable to initialize Zookeeper or Curator: " + outputZkIpAddr + ":" + outputZkPort); + LOGGER.log(Level.SEVERE, "Unable to initialize Zookeeper or Curator: {0}:{1}", new Object[]{outputZkIpAddr, outputZkPort}); + return; + } + + try { + // if output ZK database is new, we may have to ceate root "autopsy" node and it's sub-nodes + populateCategoryNodes(outputCurator); + } catch (KeeperException | CoordinationServiceException ex) { + System.out.println("Unable to initialize Curator: " + outputZkIpAddr + ":" + outputZkPort); + LOGGER.log(Level.SEVERE, "Unable to initialize Curator: {0}:{1}", new Object[]{outputZkIpAddr, outputZkPort}); + return; + } + + copyAllCategoryNodes(); + + System.out.println("Done..."); + } + + /** + * Copy all Autopsy coordination service nodes from one ZK database to + * another. + */ + private static void copyAllCategoryNodes() { + + for (CategoryNode category : CategoryNode.values()) { + List inputNodeList = Collections.EMPTY_LIST; + try { + inputNodeList = getNodeList(category); + } catch (CoordinationServiceException ex) { + System.out.println("Unable to get ZK nodes for category: " + category.getDisplayName()); + LOGGER.log(Level.SEVERE, "Unable to get ZK nodes for category: " + category.getDisplayName(), ex); + continue; + } + + for (String zkNode : inputNodeList) { + try { + final byte[] nodeBytes = getNodeData(category, zkNode); + try (Lock manifestLock = tryGetExclusiveLock(outputCurator, category, zkNode)) { + setNodeData(outputCurator, category, zkNode, nodeBytes); + } + } catch (CoordinationServiceException | InterruptedException ex) { + System.out.println("Unable to write ZK node data for node: " + zkNode); + LOGGER.log(Level.SEVERE, "Unable to write ZK node data for node: " + zkNode, ex); + continue; + } + } + } + } + + /** + * Initialize Curator framework. + * + * @param zkIpAddr Zookeeper server IP address. + * @param zookeeperPort Zookeeper server port number. + * + * @return CuratorFramework object + */ + private static CuratorFramework initializeCurator(String zkIpAddr, String zookeeperPort) { + + try { + if (!isZooKeeperAccessible(zkIpAddr, zookeeperPort)) { + System.out.println("Unable to connect to Zookeeper"); + LOGGER.log(Level.SEVERE, "Unable to connect to Zookeeper"); + return null; + } + } catch (InterruptedException | IOException ex) { + System.out.println("Unable to connect to Zookeeper"); + LOGGER.log(Level.SEVERE, "Unable to connect to Zookeeper", ex); + return null; + } + + /* + * Connect to ZooKeeper via Curator. + */ + RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); + String connectString = zkIpAddr + ":" + zookeeperPort; + CuratorFramework curator = CuratorFrameworkFactory.newClient(connectString, SESSION_TIMEOUT_MILLISECONDS, CONNECTION_TIMEOUT_MILLISECONDS, retryPolicy); + curator.start(); + return curator; + } + + /* + * Creates Autopsy coordination service root ZK nodes. + */ + private static Map populateCategoryNodes(CuratorFramework curator) throws KeeperException, CoordinationServiceException { + /* + * Create the top-level root and category nodes. + */ + String rootNodeName = DEFAULT_NAMESPACE_ROOT; + String rootNode = rootNodeName; + + if (!rootNode.startsWith("/")) { + rootNode = "/" + rootNode; + } + Map categoryPaths = new ConcurrentHashMap<>(); + for (CategoryNode node : CategoryNode.values()) { + String nodePath = rootNode + "/" + node.getDisplayName(); + try { + curator.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(nodePath); + } catch (KeeperException ex) { + if (ex.code() != KeeperException.Code.NODEEXISTS) { + throw ex; + } + } catch (Exception ex) { + throw new CoordinationServiceException("Curator experienced an error", ex); + } + categoryPaths.put(node.getDisplayName(), nodePath); + } + return categoryPaths; + } + + /** + * Determines if ZooKeeper is accessible with the current settings. Closes + * the connection prior to returning. + * + * @return true if a connection was achieved, false otherwise + * + * @throws InterruptedException + * @throws IOException + */ + private static boolean isZooKeeperAccessible(String solrIpAddr, String zookeeperPort) throws InterruptedException, IOException { + boolean result = false; + Object workerThreadWaitNotifyLock = new Object(); + String connectString = solrIpAddr + ":" + zookeeperPort; + ZooKeeper zooKeeper = new ZooKeeper(connectString, ZOOKEEPER_SESSION_TIMEOUT_MILLIS, + (WatchedEvent event) -> { + synchronized (workerThreadWaitNotifyLock) { + workerThreadWaitNotifyLock.notifyAll(); + } + }); + synchronized (workerThreadWaitNotifyLock) { + workerThreadWaitNotifyLock.wait(ZOOKEEPER_CONNECTION_TIMEOUT_MILLIS); + } + ZooKeeper.States state = zooKeeper.getState(); + if (state == ZooKeeper.States.CONNECTED || state == ZooKeeper.States.CONNECTEDREADONLY) { + result = true; + } + zooKeeper.close(); + return result; + } + + /** + * Tries to get an exclusive lock on a node path appended to a category path + * in the namespace managed by this coordination service. Returns + * immediately if the lock can not be acquired. + * + * IMPORTANT: The lock needs to be released in the same thread in which it + * is acquired. + * + * @param category The desired category in the namespace. + * @param nodePath The node path to use as the basis for the lock. + * + * @return The lock, or null if the lock could not be obtained. + * + * @throws CoordinationServiceException If there is an error during lock + * acquisition. + */ + private static Lock tryGetExclusiveLock(CuratorFramework curator, CategoryNode category, String nodePath) throws CoordinationServiceException { + String fullNodePath = getFullyQualifiedNodePath(category, nodePath); + try { + InterProcessReadWriteLock lock = new InterProcessReadWriteLock(curator, fullNodePath); + if (!lock.writeLock().acquire(0, TimeUnit.SECONDS)) { + return null; + } + return new Lock(nodePath, lock.writeLock()); + } catch (Exception ex) { + throw new CoordinationServiceException(String.format("Failed to get exclusive lock for %s", fullNodePath), ex); + } + } + + /** + * Retrieve the data associated with the specified node. + * + * @param category The desired category in the namespace. + * @param nodePath The node to retrieve the data for. + * + * @return The data associated with the node, if any, or null if the node + * has not been created yet. + * + * @throws CoordinationServiceException If there is an error setting the + * node data. + * @throws InterruptedException If interrupted while blocked during + * setting of node data. + */ + private static byte[] getNodeData(CategoryNode category, String nodePath) throws CoordinationServiceException, InterruptedException { + String fullNodePath = getFullyQualifiedNodePath(category, nodePath); + try { + return inputCurator.getData().forPath(fullNodePath); + } catch (NoNodeException ex) { + return null; + } catch (Exception ex) { + if (ex instanceof InterruptedException) { + throw (InterruptedException) ex; + } else { + throw new CoordinationServiceException(String.format("Failed to get data for %s", fullNodePath), ex); + } + } + } + + /** + * Store the given data with the specified node. + * + * @param category The desired category in the namespace. + * @param nodePath The node to associate the data with. + * @param data The data to store with the node. + * + * @throws CoordinationServiceException If there is an error setting the + * node data. + * @throws InterruptedException If interrupted while blocked during + * setting of node data. + */ + private static void setNodeData(CuratorFramework curator, CategoryNode category, String nodePath, byte[] data) throws CoordinationServiceException, InterruptedException { + String fullNodePath = getFullyQualifiedNodePath(category, nodePath); + try { + curator.setData().forPath(fullNodePath, data); + } catch (Exception ex) { + if (ex instanceof InterruptedException) { + throw (InterruptedException) ex; + } else { + throw new CoordinationServiceException(String.format("Failed to set data for %s", fullNodePath), ex); + } + } + } + + /** + * Delete the specified node. + * + * @param category The desired category in the namespace. + * @param nodePath The node to delete. + * + * @throws CoordinationServiceException If there is an error setting the + * node data. + * @throws InterruptedException If interrupted while blocked during + * setting of node data. + */ + private static void deleteNode(CategoryNode category, String nodePath) throws CoordinationServiceException, InterruptedException { + String fullNodePath = getFullyQualifiedNodePath(category, nodePath); + try { + inputCurator.delete().forPath(fullNodePath); + } catch (Exception ex) { + if (ex instanceof InterruptedException) { + throw (InterruptedException) ex; + } else { + throw new CoordinationServiceException(String.format("Failed to set data for %s", fullNodePath), ex); + } + } + } + + /** + * Gets a list of the child nodes of a category in the namespace. + * + * @param category The desired category in the namespace. + * + * @return A list of child node names. + * + * @throws CoordinationServiceException If there is an error getting the + * node list. + */ + private static List getNodeList(CategoryNode category) throws CoordinationServiceException { + try { + List list = inputCurator.getChildren().forPath(categoryNodeToPath.get(category.getDisplayName())); + return list; + } catch (Exception ex) { + throw new CoordinationServiceException(String.format("Failed to get node list for %s", category.getDisplayName()), ex); + } + } + + /** + * Creates a node path within a given category. + * + * @param category A category node. + * @param nodePath A node path relative to a category node path. + * + * @return + */ + private static String getFullyQualifiedNodePath(CategoryNode category, String nodePath) { + // nodePath on Unix systems starts with a "/" and ZooKeeper doesn't like two slashes in a row + if (nodePath.startsWith("/")) { + return categoryNodeToPath.get(category.getDisplayName()) + nodePath.toUpperCase(); + } else { + return categoryNodeToPath.get(category.getDisplayName()) + "/" + nodePath.toUpperCase(); + } + } + + /** + * Exception type thrown by the coordination service. + */ + private final static class CoordinationServiceException extends Exception { + + private static final long serialVersionUID = 1L; + + private CoordinationServiceException(String message) { + super(message); + } + + private CoordinationServiceException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * An opaque encapsulation of a lock for use in distributed synchronization. + * Instances are obtained by calling a get lock method and must be passed to + * a release lock method. + */ + private static class Lock implements AutoCloseable { + + /** + * This implementation uses the Curator read/write lock. see + * http://curator.apache.org/curator-recipes/shared-reentrant-read-write-lock.html + */ + private final InterProcessMutex interProcessLock; + private final String nodePath; + + private Lock(String nodePath, InterProcessMutex lock) { + this.nodePath = nodePath; + this.interProcessLock = lock; + } + + public String getNodePath() { + return nodePath; + } + + public void release() throws CoordinationServiceException { + try { + this.interProcessLock.release(); + } catch (Exception ex) { + throw new CoordinationServiceException(String.format("Failed to release the lock on %s", nodePath), ex); + } + } + + @Override + public void close() throws CoordinationServiceException { + release(); + } + } + + /** + * Category nodes are the immediate children of the root node of a shared + * hierarchical namespace managed by a coordination service. + */ + public enum CategoryNode { + + CASES("cases"), + MANIFESTS("manifests"), + CONFIG("config"), + CENTRAL_REPO("centralRepository"), + HEALTH_MONITOR("healthMonitor"); + + private final String displayName; + + private CategoryNode(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/build.xml b/build.xml index a7c64ef4ba..7377d59c04 100644 --- a/build.xml +++ b/build.xml @@ -106,8 +106,7 @@ - - + @@ -115,6 +114,14 @@ + + + + + + + +