You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by br...@apache.org on 2016/11/11 19:27:21 UTC
nifi git commit: NIFI-2957 ZooKeeper Migration Toolkit
Repository: nifi
Updated Branches:
refs/heads/master 06f191ca1 -> ed6e03399
NIFI-2957 ZooKeeper Migration Toolkit
Reads from and writes to a Zookeeper that is open or secured via user/password digest or SASL
Supports persisting of data obtained from Zookeeper to a file or standard out as JSON
Supports sending of Zookeeper data to Zookeeper from a file or standard out as JSON
Does not allow data obtained from a source Zookeeper to be written back to the same Zookeeper connect string and path
This closes #1193
Signed-off-by: Bryan Rosander <br...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/ed6e0339
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/ed6e0339
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/ed6e0339
Branch: refs/heads/master
Commit: ed6e03399f63d53aafdc2faadfa0878ff0b42188
Parents: 06f191c
Author: Jeff Storck <jt...@gmail.com>
Authored: Thu Oct 27 12:21:20 2016 -0400
Committer: Bryan Rosander <br...@apache.org>
Committed: Fri Nov 11 14:24:56 2016 -0500
----------------------------------------------------------------------
nifi-toolkit/nifi-toolkit-assembly/pom.xml | 4 +
.../src/main/resources/bin/zk-migrator.bat | 40 +++
.../src/main/resources/bin/zk-migrator.sh | 120 ++++++++
.../src/main/resources/classpath/logback.groovy | 39 +++
.../nifi-toolkit-zookeeper-migrator/pom.xml | 102 +++++++
.../toolkit/zkmigrator/DataStatAclNode.java | 85 ++++++
.../zkmigrator/ZooKeeperEndpointConfig.java | 66 ++++
.../toolkit/zkmigrator/ZooKeeperMigrator.java | 299 +++++++++++++++++++
.../zkmigrator/ZooKeeperMigratorMain.java | 148 +++++++++
.../nifi/toolkit/zkmigrator/ZooKeeperNode.java | 66 ++++
.../src/main/resources/logback.groovy | 39 +++
.../zkmigrator/ZooKeeperMigratorTest.groovy | 218 ++++++++++++++
.../src/test/resources/logback.groovy | 39 +++
.../src/test/resources/test-data-user-pass.json | 54 ++++
.../src/test/resources/test-data.json | 191 ++++++++++++
nifi-toolkit/pom.xml | 1 +
pom.xml | 10 +
17 files changed, 1521 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-assembly/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-assembly/pom.xml b/nifi-toolkit/nifi-toolkit-assembly/pom.xml
index 8d189e4..2986fbe 100644
--- a/nifi-toolkit/nifi-toolkit-assembly/pom.xml
+++ b/nifi-toolkit/nifi-toolkit-assembly/pom.xml
@@ -73,6 +73,10 @@ language governing permissions and limitations under the License. -->
<artifactId>nifi-toolkit-s2s</artifactId>
</dependency>
<dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-toolkit-zookeeper-migrator</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>compile</scope>
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/zk-migrator.bat
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/zk-migrator.bat b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/zk-migrator.bat
new file mode 100644
index 0000000..86bd480
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/zk-migrator.bat
@@ -0,0 +1,40 @@
+@echo off
+rem
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements. See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License. You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+rem
+
+rem Use JAVA_HOME if it's set; otherwise, just use java
+
+if "%JAVA_HOME%" == "" goto noJavaHome
+if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
+set JAVA_EXE=%JAVA_HOME%\bin\java.exe
+goto startConfig
+
+:noJavaHome
+echo The JAVA_HOME environment variable is not defined correctly.
+echo Instead the PATH will be used to find the java executable.
+echo.
+set JAVA_EXE=java
+goto startConfig
+
+:startConfig
+set LIB_DIR=%~dp0..\classpath;%~dp0..\lib
+
+SET JAVA_PARAMS=-cp %LIB_DIR%\* -Xms12m -Xmx24m %JAVA_ARGS% org.apache.nifi.toolkit.zkmigrator.ZooKeeperMigratorMain
+
+cmd.exe /C "%JAVA_EXE%" %JAVA_PARAMS% %*
+
+popd
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/zk-migrator.sh
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/zk-migrator.sh b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/zk-migrator.sh
new file mode 100644
index 0000000..e805800
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/bin/zk-migrator.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+#
+
+# Script structure inspired from Apache Karaf and other Apache projects with similar startup approaches
+
+SCRIPT_DIR=$(dirname "$0")
+SCRIPT_NAME=$(basename "$0")
+NIFI_TOOLKIT_HOME=$(cd "${SCRIPT_DIR}" && cd .. && pwd)
+PROGNAME=$(basename "$0")
+
+
+warn() {
+ (>&2 echo "${PROGNAME}: $*")
+}
+
+die() {
+ warn "$*"
+ exit 1
+}
+
+detectOS() {
+ # OS specific support (must be 'true' or 'false').
+ cygwin=false;
+ aix=false;
+ os400=false;
+ darwin=false;
+ case "$(uname)" in
+ CYGWIN*)
+ cygwin=true
+ ;;
+ AIX*)
+ aix=true
+ ;;
+ OS400*)
+ os400=true
+ ;;
+ Darwin)
+ darwin=true
+ ;;
+ esac
+ # For AIX, set an environment variable
+ if ${aix}; then
+ export LDR_CNTRL=MAXDATA=0xB0000000@DSA
+ echo ${LDR_CNTRL}
+ fi
+}
+
+locateJava() {
+ # Setup the Java Virtual Machine
+ if $cygwin ; then
+ [ -n "${JAVA}" ] && JAVA=$(cygpath --unix "${JAVA}")
+ [ -n "${JAVA_HOME}" ] && JAVA_HOME=$(cygpath --unix "${JAVA_HOME}")
+ fi
+
+ if [ "x${JAVA}" = "x" ] && [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+ if [ "x${JAVA}" = "x" ]; then
+ if [ "x${JAVA_HOME}" != "x" ]; then
+ if [ ! -d "${JAVA_HOME}" ]; then
+ die "JAVA_HOME is not valid: ${JAVA_HOME}"
+ fi
+ JAVA="${JAVA_HOME}/bin/java"
+ else
+ warn "JAVA_HOME not set; results may vary"
+ JAVA=$(type java)
+ JAVA=$(expr "${JAVA}" : '.* \(/.*\)$')
+ if [ "x${JAVA}" = "x" ]; then
+ die "java command not found"
+ fi
+ fi
+ fi
+}
+
+init() {
+ # Determine if there is special OS handling we must perform
+ detectOS
+
+ # Locate the Java VM to execute
+ locateJava "$1"
+}
+
+run() {
+ LIBS="${NIFI_TOOLKIT_HOME}/lib/*"
+
+ sudo_cmd_prefix=""
+ if $cygwin; then
+ NIFI_TOOLKIT_HOME=$(cygpath --path --windows "${NIFI_TOOLKIT_HOME}")
+ CLASSPATH="$NIFI_TOOLKIT_HOME/classpath";$(cygpath --path --windows "${LIBS}")
+ else
+ CLASSPATH="$NIFI_TOOLKIT_HOME/classpath:${LIBS}"
+ fi
+
+ export JAVA_HOME="$JAVA_HOME"
+ export NIFI_TOOLKIT_HOME="$NIFI_TOOLKIT_HOME"
+
+ umask 0077
+ "${JAVA}" -cp "${CLASSPATH}" -Xms12m -Xmx24m org.apache.nifi.toolkit.zkmigrator.ZooKeeperMigratorMain "$@"
+ return $?
+}
+
+
+init "$1"
+run "$@"
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/classpath/logback.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/classpath/logback.groovy b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/classpath/logback.groovy
new file mode 100644
index 0000000..ac77f24
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-assembly/src/main/resources/classpath/logback.groovy
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder
+import ch.qos.logback.core.ConsoleAppender
+import ch.qos.logback.core.status.NopStatusListener
+
+statusListener(NopStatusListener)
+
+appender('stdout', ConsoleAppender) {
+ target = 'System.out'
+ encoder(PatternLayoutEncoder) {
+ pattern = "%date %level [%thread] %logger{40} %msg%n"
+ }
+}
+
+appender('stderr', ConsoleAppender) {
+ target = 'System.err'
+ encoder(PatternLayoutEncoder) {
+ pattern = "%date %level [%thread] %logger{40} %msg%n"
+ }
+}
+
+logger("org.apache.nifi.toolkit.zkmigrator", INFO)
+logger("org.apache.zookeeper", WARN)
+root(WARN, ['stderr'])
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/pom.xml b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/pom.xml
new file mode 100644
index 0000000..6def98b
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>nifi-toolkit-zookeeper-migrator</artifactId>
+
+ <parent>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-toolkit</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ </parent>
+
+ <dependencies>
+ <dependency>
+ <groupId>commons-cli</groupId>
+ <artifactId>commons-cli</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.zookeeper</groupId>
+ <artifactId>zookeeper</artifactId>
+ <!--<exclusions>-->
+ <!-- these exclusios can be used once the ZK dependency is upgraded to 3.5.2 or 3.6.0
+ which do not have the hard dependnecy on log4j -->
+ <!--<exclusion>-->
+ <!--<groupId>log4j</groupId>-->
+ <!--<artifactId>log4j</artifactId>-->
+ <!--</exclusion>-->
+ <!--<exclusion>-->
+ <!--<groupId>org.slf4j</groupId>-->
+ <!--<artifactId>slf4j-api</artifactId>-->
+ <!--</exclusion>-->
+ <!--<exclusion>-->
+ <!--<groupId>org.slf4j</groupId>-->
+ <!--<artifactId>slf4j-log4j12</artifactId>-->
+ <!--</exclusion>-->
+ <!--</exclusions>-->
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-all</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.curator</groupId>
+ <artifactId>curator-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes combine.children="append">
+ <exclude>src/test/resources/test-data.json</exclude>
+ <exclude>src/test/resources/test-data-user-pass.json</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/DataStatAclNode.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/DataStatAclNode.java b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/DataStatAclNode.java
new file mode 100644
index 0000000..9d7d081
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/DataStatAclNode.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.toolkit.zkmigrator;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Stat;
+
+import java.util.List;
+import java.util.Objects;
+
+public class DataStatAclNode {
+
+ private final String path;
+ private final byte[] data;
+ private final Stat stat;
+ private final List<ACL> acls;
+ private final long ephemeralOwner;
+
+ public DataStatAclNode(String path, byte[] data, Stat stat, List<ACL> acls, long ephemeralOwner) {
+ this.path = Preconditions.checkNotNull(path, "path can not be null");
+ this.data = data;
+ this.stat = Preconditions.checkNotNull(stat, "stat can not be null");
+ this.acls = acls == null ? ImmutableList.of() : ImmutableList.copyOf(acls);
+ this.ephemeralOwner = ephemeralOwner;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public Stat getStat() {
+ return stat;
+ }
+
+ public List<ACL> getAcls() {
+ return acls;
+ }
+
+ public long getEphemeralOwner() {
+ return ephemeralOwner;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DataStatAclNode that = (DataStatAclNode) o;
+ return Objects.equals(path, that.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(path);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("path", path)
+ .add("acls", acls)
+ .add("ephemeralOwner", ephemeralOwner)
+ .toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperEndpointConfig.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperEndpointConfig.java b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperEndpointConfig.java
new file mode 100644
index 0000000..c5b40b2
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperEndpointConfig.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.toolkit.zkmigrator;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+
+import java.util.Objects;
+
+class ZooKeeperEndpointConfig {
+ private final String connectString;
+ private final String path;
+
+ ZooKeeperEndpointConfig(String connectString, String path) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(connectString), "connectString can not be null or empty");
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(path), "path can not be null or empty");
+ this.connectString = connectString;
+ this.path = '/' + Joiner.on('/').join(Splitter.on('/').omitEmptyStrings().trimResults().split(path));
+ }
+
+ public String getConnectString() {
+ return connectString;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ZooKeeperEndpointConfig that = (ZooKeeperEndpointConfig) o;
+ return Objects.equals(connectString, that.connectString) && Objects.equals(path, that.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(connectString, path);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("connectString", connectString)
+ .add("path", path)
+ .toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigrator.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigrator.java b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigrator.java
new file mode 100644
index 0000000..ab110a6
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigrator.java
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.toolkit.zkmigrator;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParser;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Stat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Spliterators;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+class ZooKeeperMigrator {
+
+ enum AuthMode {OPEN, DIGEST, SASL}
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ZooKeeperMigrator.class);
+ private static final String SCHEME_DIGEST = AuthMode.DIGEST.name().toLowerCase();
+
+ private final ZooKeeperEndpointConfig zooKeeperEndpointConfig;
+
+ ZooKeeperMigrator(String zookeeperEndpoint) throws URISyntaxException {
+ LOGGER.debug("ZooKeeper endpoint parameter: {}", zookeeperEndpoint);
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(zookeeperEndpoint), "connectString must not be null");
+ final String[] connectStringPath = zookeeperEndpoint.split("/", 2);
+ Preconditions.checkArgument(connectStringPath.length >= 1, "invalid ZooKeeper endpoint: %s", zookeeperEndpoint);
+ final String connectString = connectStringPath[0];
+ final String path;
+ if (connectStringPath.length == 2) {
+ path = connectStringPath[1];
+ } else {
+ path = "";
+ }
+ this.zooKeeperEndpointConfig = new ZooKeeperEndpointConfig(connectString, "/" + path);
+ }
+
+ void readZooKeeper(OutputStream zkData, AuthMode authMode, byte[] authData) throws IOException, KeeperException, InterruptedException, ExecutionException {
+ ZooKeeper zooKeeper = getZooKeeper(zooKeeperEndpointConfig, authMode, authData);
+ JsonWriter jsonWriter = new JsonWriter(new BufferedWriter(new OutputStreamWriter(zkData)));
+ jsonWriter.setIndent(" ");
+ JsonParser jsonParser = new JsonParser();
+ Gson gson = new GsonBuilder().create();
+
+ jsonWriter.beginArray();
+
+ // persist source ZooKeeperEndpointConfig
+ gson.toJson(jsonParser.parse(gson.toJson(zooKeeperEndpointConfig)).getAsJsonObject(), jsonWriter);
+
+ LOGGER.info("Persisting data from source ZooKeeper: {}", zooKeeperEndpointConfig);
+ final List<CompletableFuture<Void>> readFutures = streamPaths(getNode(zooKeeper, zooKeeperEndpointConfig.getPath()))
+ .parallel()
+ .map(node ->
+ CompletableFuture.supplyAsync(() -> {
+ final DataStatAclNode dataStatAclNode = retrieveNode(zooKeeper, node);
+ LOGGER.debug("retrieved node {} from {}", dataStatAclNode, zooKeeperEndpointConfig);
+ return dataStatAclNode;
+ }).thenAccept(dataStatAclNode -> {
+ // persist each zookeeper node
+ synchronized (jsonWriter) {
+ gson.toJson(jsonParser.parse(gson.toJson(dataStatAclNode)).getAsJsonObject(), jsonWriter);
+ }
+ })
+ ).collect(Collectors.toList());
+
+ CompletableFuture<Void> allReadsFuture = CompletableFuture.allOf(readFutures.toArray(new CompletableFuture[readFutures.size()]));
+ final CompletableFuture<List<Void>> finishedReads = allReadsFuture
+ .thenApply(v -> readFutures.stream()
+ .map(CompletableFuture::join)
+ .collect(Collectors.toList()));
+ final List<Void> readsDone = finishedReads.get();
+ jsonWriter.endArray();
+ jsonWriter.close();
+ if (LOGGER.isInfoEnabled()) {
+ final int readCount = readsDone.size();
+ LOGGER.info("{} {} read from {}", readCount, readCount == 1 ? "node" : "nodes", zooKeeperEndpointConfig);
+ }
+ }
+
+ void writeZooKeeper(InputStream zkData, AuthMode authMode, byte[] authData) throws IOException, ExecutionException, InterruptedException {
+ ZooKeeper zooKeeper = getZooKeeper(zooKeeperEndpointConfig, authMode, authData);
+ JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader(zkData)));
+ Gson gson = new GsonBuilder().create();
+
+ jsonReader.beginArray();
+
+ // determine source ZooKeeperEndpointConfig for this data
+ final ZooKeeperEndpointConfig sourceZooKeeperEndpointConfig = gson.fromJson(jsonReader, ZooKeeperEndpointConfig.class);
+ LOGGER.info("Source data was obtained from ZooKeeper: {}", sourceZooKeeperEndpointConfig);
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(sourceZooKeeperEndpointConfig.getConnectString()) && !Strings.isNullOrEmpty(sourceZooKeeperEndpointConfig.getPath()),
+ "Source ZooKeeper %s from %s is invalid", sourceZooKeeperEndpointConfig, zkData);
+ Preconditions.checkState(!zooKeeperEndpointConfig.equals(sourceZooKeeperEndpointConfig),
+ "Source ZooKeeper config %s for the data provided can not be the same as the configured destionation ZooKeeper config %s",
+ sourceZooKeeperEndpointConfig, zooKeeperEndpointConfig);
+
+ // stream through each node read from the json input
+ final Stream<DataStatAclNode> stream = StreamSupport.stream(new Spliterators.AbstractSpliterator<DataStatAclNode>(0, 0) {
+ @Override
+ public boolean tryAdvance(Consumer<? super DataStatAclNode> action) {
+ try {
+ // stream each DataStatAclNode from configured json file
+ synchronized (jsonReader) {
+ if (jsonReader.hasNext()) {
+ action.accept(gson.fromJson(jsonReader, DataStatAclNode.class));
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("unable to read nodes from json", e);
+ }
+ }
+ }, false);
+
+ final List<CompletableFuture<Stat>> writeFutures = stream.parallel().map(node -> {
+ /*
+ * create stage to migrate paths and ACLs based on the migration parent path plus the node path and the given AuthMode,
+ * this stage must be run first
+ */
+ final CompletableFuture<DataStatAclNode> transformNodeStage = CompletableFuture.supplyAsync(() -> transformNode(node, authMode));
+ /*
+ * create stage to ensure that nodes exist for the entire path of the zookeeper node, must be invoked after the transformNode stage to
+ * ensure that the node will exist after path migration
+ */
+ final Function<DataStatAclNode, String> ensureNodeExistsStage = dataStatAclNode ->
+ ensureNodeExists(zooKeeper, dataStatAclNode.getPath(), dataStatAclNode.getEphemeralOwner() == 0 ? CreateMode.PERSISTENT : CreateMode.EPHEMERAL);
+ /*
+ * create stage that waits for both the transformNode and ensureNodeExists stages complete, and also provides that the given transformed node is
+ * available to the next stage
+ */
+ final BiFunction<String, DataStatAclNode, DataStatAclNode> combineEnsureNodeAndTransferNodeStage = (u, dataStatAclNode) -> dataStatAclNode;
+ /*
+ * create stage to transmit the node to the destination zookeeper endpoint, must be invoked after the node has been transformed and its path
+ * has been created (or already exists) in the destination zookeeper
+ */
+ final Function<DataStatAclNode, CompletionStage<Stat>> transmitNodeStage = dataStatNode ->
+ CompletableFuture.supplyAsync(() -> transmitNode(zooKeeper, dataStatNode));
+ /*
+ * submit the stages chained together in the proper order to perform the processing on the given node
+ */
+ return transformNodeStage.thenApply(ensureNodeExistsStage).thenCombine(transformNodeStage, combineEnsureNodeAndTransferNodeStage).thenCompose(transmitNodeStage);
+ }).collect(Collectors.toList());
+
+ CompletableFuture<Void> allWritesFuture = CompletableFuture.allOf(writeFutures.toArray(new CompletableFuture[writeFutures.size()]));
+ final CompletableFuture<List<Stat>> finishedWrites = allWritesFuture
+ .thenApply(v -> writeFutures.stream()
+ .map(CompletableFuture::join)
+ .collect(Collectors.toList()));
+ final List<Stat> writesDone = finishedWrites.get();
+ if (LOGGER.isInfoEnabled()) {
+ final int writeCount = writesDone.size();
+ LOGGER.info("{} {} transferred to {}", writeCount, writeCount == 1 ? "node" : "nodes", zooKeeperEndpointConfig);
+ }
+ jsonReader.close();
+ }
+
+ private Stream<String> streamPaths(ZooKeeperNode node) {
+ return Stream.concat(Stream.of(node.getPath()), node.getChildren().stream().flatMap(this::streamPaths));
+ }
+
+ private ZooKeeperNode getNode(ZooKeeper zooKeeper, String path) throws KeeperException, InterruptedException {
+ LOGGER.debug("retrieving node and children at {}", path);
+ final List<String> children = zooKeeper.getChildren(path, false);
+ return new ZooKeeperNode(path, children.stream().map(s -> {
+ final String childPath = Joiner.on('/').skipNulls().join(path.equals("/") ? "" : path, s);
+ try {
+ return getNode(zooKeeper, childPath);
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("unable to discover sub-tree from %s", childPath), e);
+ }
+ }).collect(Collectors.toList()));
+ }
+
+ private DataStatAclNode retrieveNode(ZooKeeper zooKeeper, String path) {
+ Preconditions.checkNotNull(zooKeeper, "ZooKeeper client must not be null");
+ Preconditions.checkNotNull(path, "path must not be null");
+ final Stat stat = new Stat();
+ final byte[] data;
+ final List<ACL> acls;
+ final long ephemeralOwner;
+ try {
+ data = zooKeeper.getData(path, false, stat);
+ acls = zooKeeper.getACL(path, stat);
+ ephemeralOwner = stat.getEphemeralOwner();
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("unable to get data, ACLs, and stats from %s for node at path %s", zooKeeper, path), e);
+ }
+ return new DataStatAclNode(path, data, stat, acls, ephemeralOwner);
+ }
+
+ private String ensureNodeExists(ZooKeeper zooKeeper, String path, CreateMode createMode) {
+ try {
+ LOGGER.debug("attempting to create node at {}", path);
+ final ArrayList<ACL> acls = ZooDefs.Ids.OPEN_ACL_UNSAFE;
+ final String createNodePath = zooKeeper.create(path, new byte[0], acls, createMode);
+ LOGGER.info("created node at {}, acls: {}, createMode: {}", createNodePath, acls, createMode);
+ return createNodePath;
+ } catch (KeeperException e) {
+ if (KeeperException.Code.NONODE.equals(e.code())) {
+ final List<String> pathTokens = Splitter.on('/').omitEmptyStrings().trimResults().splitToList(path);
+ final String parentPath = "/" + Joiner.on('/').skipNulls().join(pathTokens.subList(0, pathTokens.size() - 1));
+ LOGGER.debug("node doesn't exist, recursively attempting to create node at {}", parentPath);
+ ensureNodeExists(zooKeeper, parentPath, CreateMode.PERSISTENT);
+ LOGGER.debug("recursively created node at {}", parentPath);
+ LOGGER.debug("retrying attempt to create node at {}", path);
+ return ensureNodeExists(zooKeeper, path, createMode);
+ } else if (KeeperException.Code.NODEEXISTS.equals(e.code())) {
+ return path;
+ } else {
+ throw new RuntimeException(String.format("unable to create node at path %s, ZooKeeper returned %s", path, e.code()), e);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(String.format("unable to create node at path %s", path), e);
+ }
+ }
+
+ private DataStatAclNode transformNode(DataStatAclNode node, AuthMode destinationAuthMode) {
+ String migrationPath = '/' + Joiner.on('/').skipNulls().join(Splitter.on('/').omitEmptyStrings().trimResults().split(zooKeeperEndpointConfig.getPath() + node.getPath()));
+ // For the NiFi use case, all nodes will be migrated to CREATOR_ALL_ACL
+ final DataStatAclNode migratedNode = new DataStatAclNode(migrationPath, node.getData(), node.getStat(),
+ destinationAuthMode.equals(AuthMode.OPEN) ? ZooDefs.Ids.OPEN_ACL_UNSAFE : ZooDefs.Ids.CREATOR_ALL_ACL,
+ node.getEphemeralOwner());
+ LOGGER.info("transformed original node {} to {}", node, migratedNode);
+ return migratedNode;
+ }
+
+ private Stat transmitNode(ZooKeeper zooKeeper, DataStatAclNode node) {
+ Preconditions.checkNotNull(zooKeeper, "zooKeeper must not be null");
+ Preconditions.checkNotNull(node, "node must not be null");
+ try {
+ LOGGER.debug("attempting to transfer node to {} with ACL {}: {}", zooKeeperEndpointConfig, node.getAcls(), node);
+ // set data without caring what the previous version of the data at that path
+ zooKeeper.setData(node.getPath(), node.getData(), -1);
+ zooKeeper.setACL(node.getPath(), node.getAcls(), -1);
+ LOGGER.info("transfered node {} in {}", node, zooKeeperEndpointConfig);
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("unable to transmit data to %s for path %s", zooKeeper, node.getPath()), e);
+ }
+ return node.getStat();
+ }
+
+ private ZooKeeper getZooKeeper(ZooKeeperEndpointConfig zooKeeperEndpointConfig, AuthMode authMode, byte[] authData) throws IOException {
+ ZooKeeper zooKeeper = new ZooKeeper(zooKeeperEndpointConfig.getConnectString(), 3000, watchedEvent -> {
+ });
+ if (authMode.equals(AuthMode.DIGEST)) {
+ zooKeeper.addAuthInfo(SCHEME_DIGEST, authData);
+ }
+ return zooKeeper;
+ }
+
+ ZooKeeperEndpointConfig getZooKeeperEndpointConfig() {
+ return zooKeeperEndpointConfig;
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigratorMain.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigratorMain.java b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigratorMain.java
new file mode 100644
index 0000000..1dbdcca
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigratorMain.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.toolkit.zkmigrator;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.nifi.toolkit.zkmigrator.ZooKeeperMigrator.AuthMode;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+
+public class ZooKeeperMigratorMain {
+
+ enum Mode {READ, WRITE}
+
+ private static final Option OPTION_ZK_MIGRATOR_HELP = Option.builder("h")
+ .longOpt("help")
+ .desc("display help/usage info")
+ .build();
+ private static final Option OPTION_ZK_ENDPOINT = Option.builder("z")
+ .longOpt("zookeeper")
+ .desc("ZooKeeper connect string with path (ex. host:port/path)")
+ .hasArg()
+ .argName("connect-string")
+ .required()
+ .build();
+ private static final Option OPTION_RECEIVE = Option.builder("r")
+ .longOpt("receive")
+ .desc("receives data from zookeeper and writes to the given filename or standard output")
+ .build();
+ private static final Option OPTION_SEND = Option.builder("s")
+ .longOpt("send")
+ .desc("sends data to zookeeper read from the given filename or standard input")
+ .build();
+ private static final Option OPTION_ZK_AUTH_INFO = Option.builder("a")
+ .longOpt("auth")
+ .desc("username and password for the given ZK path")
+ .hasArg()
+ .argName("username:password")
+ .build();
+ private static final Option OPTION_ZK_KRB_CONF_FILE = Option.builder("k")
+ .longOpt("krb-conf")
+ .desc("JAAS file containing Kerberos config")
+ .hasArg()
+ .argName("jaas-filename")
+ .numberOfArgs(1)
+ .build();
+ private static final Option OPTION_FILE = Option.builder("f")
+ .longOpt("file")
+ .desc("file to be used for ZooKeeper data")
+ .hasArg()
+ .argName("filename")
+ .build();
+
+ private static Options createOptions() {
+ final Options options = new Options();
+ options.addOption(OPTION_ZK_MIGRATOR_HELP);
+ options.addOption(OPTION_ZK_ENDPOINT);
+ options.addOption(OPTION_ZK_AUTH_INFO);
+ options.addOption(OPTION_FILE);
+ final OptionGroup optionGroupAuth = new OptionGroup().addOption(OPTION_ZK_AUTH_INFO).addOption(OPTION_ZK_KRB_CONF_FILE);
+ optionGroupAuth.setRequired(false);
+ options.addOptionGroup(optionGroupAuth);
+ final OptionGroup optionGroupReadWrite = new OptionGroup().addOption(OPTION_RECEIVE).addOption(OPTION_SEND);
+ optionGroupReadWrite.setRequired(true);
+ options.addOptionGroup(optionGroupReadWrite);
+
+ return options;
+ }
+
+ private static void printUsage(String errorMessage, Options options) {
+ Preconditions.checkNotNull(options, "command line options were not specified");
+ if (errorMessage != null) {
+ System.out.println(String.format("%s\n", errorMessage));
+ }
+ HelpFormatter helpFormatter = new HelpFormatter();
+ helpFormatter.setWidth(160);
+ helpFormatter.printHelp(ZooKeeperMigratorMain.class.getCanonicalName(), options, true);
+ }
+
+ public static void main(String[] args) {
+ PrintStream output = System.out;
+ System.setOut(System.err);
+
+ final Options options = createOptions();
+ final CommandLine commandLine;
+ try {
+ commandLine = new DefaultParser().parse(options, args);
+ if (commandLine.hasOption(OPTION_ZK_MIGRATOR_HELP.getLongOpt())) {
+ printUsage(null, options);
+ } else {
+ final String zookeeperUri = commandLine.getOptionValue(OPTION_ZK_ENDPOINT.getOpt());
+ final Mode mode = commandLine.hasOption(OPTION_RECEIVE.getOpt()) ? Mode.READ : Mode.WRITE;
+ final String filename = commandLine.getOptionValue(OPTION_FILE.getOpt());
+ final String auth = commandLine.getOptionValue(OPTION_ZK_AUTH_INFO.getOpt());
+ final String jaasFilename = commandLine.getOptionValue(OPTION_ZK_KRB_CONF_FILE.getOpt());
+ final AuthMode authMode;
+ final byte[] authData;
+ if (auth != null) {
+ authMode = AuthMode.DIGEST;
+ authData = auth.getBytes(StandardCharsets.UTF_8);
+ } else {
+ authData = null;
+ if (!Strings.isNullOrEmpty(jaasFilename)) {
+ authMode = AuthMode.SASL;
+ System.setProperty("java.security.auth.login.config", jaasFilename);
+ } else {
+ authMode = AuthMode.OPEN;
+ }
+ }
+ final ZooKeeperMigrator zookeeperMigrator = new ZooKeeperMigrator(zookeeperUri);
+ if (mode.equals(Mode.READ)) {
+ zookeeperMigrator.readZooKeeper(filename != null ? new FileOutputStream(Paths.get(filename).toFile()) : output, authMode, authData);
+ } else {
+ zookeeperMigrator.writeZooKeeper(filename != null ? new FileInputStream(Paths.get(filename).toFile()) : System.in, authMode, authData);
+ }
+ }
+ } catch (ParseException e) {
+ printUsage(e.getLocalizedMessage(), options);
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("unable to perform operation: %s", e.getLocalizedMessage()), e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperNode.java
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperNode.java b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperNode.java
new file mode 100644
index 0000000..dd97e06
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperNode.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.toolkit.zkmigrator;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.Objects;
+
+class ZooKeeperNode {
+
+ private final String path;
+ private final List<ZooKeeperNode> children;
+
+ public ZooKeeperNode(String path, List<ZooKeeperNode> children) {
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(path), "path can not be null or empty");
+ this.path = path;
+ this.children = children == null ? ImmutableList.of() : ImmutableList.copyOf(children);
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public List<ZooKeeperNode> getChildren() {
+ return children;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ZooKeeperNode node = (ZooKeeperNode) o;
+ return Objects.equals(path, node.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(path);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("path", path)
+ .add("children", children)
+ .toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/resources/logback.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/resources/logback.groovy b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/resources/logback.groovy
new file mode 100644
index 0000000..ac77f24
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/main/resources/logback.groovy
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder
+import ch.qos.logback.core.ConsoleAppender
+import ch.qos.logback.core.status.NopStatusListener
+
+statusListener(NopStatusListener)
+
+appender('stdout', ConsoleAppender) {
+ target = 'System.out'
+ encoder(PatternLayoutEncoder) {
+ pattern = "%date %level [%thread] %logger{40} %msg%n"
+ }
+}
+
+appender('stderr', ConsoleAppender) {
+ target = 'System.err'
+ encoder(PatternLayoutEncoder) {
+ pattern = "%date %level [%thread] %logger{40} %msg%n"
+ }
+}
+
+logger("org.apache.nifi.toolkit.zkmigrator", INFO)
+logger("org.apache.zookeeper", WARN)
+root(WARN, ['stderr'])
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigratorTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigratorTest.groovy b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigratorTest.groovy
new file mode 100644
index 0000000..7444fd2
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/java/org/apache/nifi/toolkit/zkmigrator/ZooKeeperMigratorTest.groovy
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.nifi.toolkit.zkmigrator
+
+import com.google.gson.Gson
+import com.google.gson.stream.JsonReader
+import org.apache.curator.test.TestingServer
+import org.apache.zookeeper.CreateMode
+import org.apache.zookeeper.WatchedEvent
+import org.apache.zookeeper.ZooDefs
+import org.apache.zookeeper.ZooKeeper
+import spock.lang.Ignore
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import java.nio.charset.StandardCharsets
+
+@Unroll
+class ZooKeeperMigratorTest extends Specification {
+
+ def "Test auth and jaas usage simultaneously"() {
+ when:
+ ZooKeeperMigratorMain.main(['-r', '-z', 'localhost:2181/path', '-a', 'user:pass', '-k', 'jaas.conf'] as String[])
+
+ then:
+ noExceptionThrown()
+ }
+
+ @Ignore
+ def "Test jaas conf on command line"() {
+ when:
+ ZooKeeperMigratorMain.main(['-r', '-z', 'localhost:2181/path', '-k', 'jaas.conf'] as String[])
+
+ then:
+ noExceptionThrown()
+ }
+
+ def "Receive from open ZooKeeper without ACL migration"() {
+ given:
+ def server = new TestingServer()
+ def client = new ZooKeeper(server.connectString, 3000, { WatchedEvent watchedEvent ->
+ })
+ def migrationPathRoot = '/nifi'
+ client.create(migrationPathRoot, 'some data'.bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
+ def subPath = '1'
+ client.create("$migrationPathRoot/$subPath", 'some data'.bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
+ subPath = '1/a'
+ client.create("$migrationPathRoot/$subPath", 'some data'.bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL)
+ subPath = '2'
+ client.create("$migrationPathRoot/$subPath", 'some data'.bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
+ subPath = '3'
+ client.create("$migrationPathRoot/$subPath", 'some data'.bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
+ def outputFilePath = 'target/test-data.json'
+
+ when:
+ ZooKeeperMigratorMain.main(['-r', '-z', "$server.connectString$migrationPathRoot", '-f', outputFilePath] as String[])
+
+ then:
+ noExceptionThrown()
+ def persistedData = new Gson().fromJson(new JsonReader(new FileReader(outputFilePath)), List) as List
+ 6 == persistedData.size();
+ }
+
+ def "Send to open ZooKeeper without ACL migration"() {
+ given:
+ def server = new TestingServer()
+ def client = new ZooKeeper(server.connectString, 3000, { WatchedEvent watchedEvent ->
+ })
+ def migrationPathRoot = '/newParent'
+
+ when:
+ ZooKeeperMigratorMain.main(['-s', '-z', "$server.connectString$migrationPathRoot", '-f', 'src/test/resources/test-data.json'] as String[])
+
+ then:
+ noExceptionThrown()
+ def nodes = getChildren(client, migrationPathRoot, [])
+ 6 == nodes.size()
+ }
+
+ def "Send to open ZooKeeper without ACL migration with new multi-node parent"() {
+ given:
+ def server = new TestingServer()
+ def client = new ZooKeeper(server.connectString, 3000, { WatchedEvent watchedEvent ->
+ })
+ def migrationPathRoot = '/newParent/node'
+
+ when:
+ ZooKeeperMigratorMain.main(['-s', '-z', "$server.connectString$migrationPathRoot", '-f', 'src/test/resources/test-data.json'] as String[])
+
+ then:
+ noExceptionThrown()
+ def nodes = getChildren(client, migrationPathRoot, [])
+ 6 == nodes.size()
+ }
+
+ def "Receive all nodes from ZooKeeper root"() {
+ given:
+ def server = new TestingServer()
+ def client = new ZooKeeper(server.connectString, 3000, { WatchedEvent watchedEvent ->
+ })
+ def migrationPathRoot = '/'
+ def addedNodePath = 'nifi'
+ client.create("$migrationPathRoot$addedNodePath", 'some data'.bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
+ def outputFilePath = 'target/test-data-root.json'
+
+ when:
+ ZooKeeperMigratorMain.main(['-r', '-z', "$server.connectString$migrationPathRoot", '-f', outputFilePath] as String[])
+
+ then:
+ noExceptionThrown()
+ def persistedData = new Gson().fromJson(new JsonReader(new FileReader(outputFilePath)), List) as List
+ 5 == persistedData.size();
+ }
+
+ def "Receive Zookeeper node created with username and password"() {
+ given:
+ def server = new TestingServer()
+ def client = new ZooKeeper(server.connectString, 3000, { WatchedEvent watchedEvent ->
+ })
+ def username = 'nifi'
+ def password = 'nifi'
+ client.addAuthInfo('digest', "$username:$password".getBytes(StandardCharsets.UTF_8))
+ def migrationPathRoot = '/nifi'
+ client.create(migrationPathRoot, 'some data'.bytes, ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT)
+ def outputFilePath = 'target/test-data-user-pass.json'
+
+ when:
+ ZooKeeperMigratorMain.main(['-r', '-z', "$server.connectString$migrationPathRoot", '-f', outputFilePath, '-a', "$username:$password"] as String[])
+
+ then:
+ noExceptionThrown()
+ def persistedData = new Gson().fromJson(new JsonReader(new FileReader(outputFilePath)), List) as List
+ 2 == persistedData.size();
+ }
+
+ def "Send to Zookeeper a node created with username and password"() {
+ given:
+ def server = new TestingServer()
+ def client = new ZooKeeper(server.connectString, 3000, { WatchedEvent watchedEvent ->
+ })
+ def username = 'nifi'
+ def password = 'nifi'
+ client.addAuthInfo('digest', "$username:$password".getBytes(StandardCharsets.UTF_8))
+ def migrationPathRoot = '/newParent'
+
+ when:
+ ZooKeeperMigratorMain.main(['-s', '-z', "$server.connectString$migrationPathRoot", '-f', 'src/test/resources/test-data-user-pass.json', '-a', "$username:$password"] as String[])
+
+ then:
+ noExceptionThrown()
+ def nodes = getChildren(client, migrationPathRoot, [])
+ 2 == nodes.size()
+ }
+
+ def "Send to open Zookeeper root"() {
+ given:
+ def server = new TestingServer()
+ def client = new ZooKeeper(server.connectString, 3000, { WatchedEvent watchedEvent ->
+ })
+ def migrationPathRoot = '/'
+
+ when:
+ ZooKeeperMigratorMain.main(['-s', '-z', "$server.connectString$migrationPathRoot", '-f', 'src/test/resources/test-data-user-pass.json'] as String[])
+
+ then:
+ noExceptionThrown()
+ def nodes = getChildren(client, migrationPathRoot, [])
+ 4 == nodes.size()
+ }
+
+ def "Parse Zookeeper connect string and path"() {
+ when:
+ def zooKeeperMigrator = new ZooKeeperMigrator("$connectStringAndPath")
+ def tokens = connectStringAndPath.split('/', 2) as List
+ def connectString = tokens[0]
+ def path = '/' + (tokens.size() > 1 ? tokens[1] : '')
+
+ then:
+ connectString == zooKeeperMigrator.getZooKeeperEndpointConfig().connectString
+ path == zooKeeperMigrator.getZooKeeperEndpointConfig().path
+
+ where:
+ connectStringAndPath || _
+ '127.0.0.1' || _
+ '127.0.0.1/' || _
+ '127.0.0.1:2181' || _
+ '127.0.0.1:2181/' || _
+ '127.0.0.1/path' || _
+ '127.0.0.1/path/node' || _
+ '127.0.0.1:2181/' || _
+ '127.0.0.1:2181/path' || _
+ '127.0.0.1:2181/path/node' || _
+ }
+
+ def List<String> getChildren(ZooKeeper client, String path, List<String> ag) {
+ def children = client.getChildren(path, null)
+ ag.add path
+ children.forEach {
+ def childPath = "/${(path.tokenize('/') + it).join('/')}"
+ getChildren(client, childPath, ag)
+ }
+ ag
+ }
+}
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/logback.groovy
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/logback.groovy b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/logback.groovy
new file mode 100644
index 0000000..ac77f24
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/logback.groovy
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder
+import ch.qos.logback.core.ConsoleAppender
+import ch.qos.logback.core.status.NopStatusListener
+
+statusListener(NopStatusListener)
+
+appender('stdout', ConsoleAppender) {
+ target = 'System.out'
+ encoder(PatternLayoutEncoder) {
+ pattern = "%date %level [%thread] %logger{40} %msg%n"
+ }
+}
+
+appender('stderr', ConsoleAppender) {
+ target = 'System.err'
+ encoder(PatternLayoutEncoder) {
+ pattern = "%date %level [%thread] %logger{40} %msg%n"
+ }
+}
+
+logger("org.apache.nifi.toolkit.zkmigrator", INFO)
+logger("org.apache.zookeeper", WARN)
+root(WARN, ['stderr'])
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/test-data-user-pass.json
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/test-data-user-pass.json b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/test-data-user-pass.json
new file mode 100644
index 0000000..e38bd91
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/test-data-user-pass.json
@@ -0,0 +1,54 @@
+[
+ {
+ "connectString": "127.0.0.1:62406",
+ "path": "/nifi",
+ "auth": [
+ 110,
+ 105,
+ 102,
+ 105,
+ 58,
+ 110,
+ 105,
+ 102,
+ 105
+ ]
+ },
+ {
+ "path": "/nifi",
+ "data": [
+ 115,
+ 111,
+ 109,
+ 101,
+ 32,
+ 100,
+ 97,
+ 116,
+ 97
+ ],
+ "stat": {
+ "czxid": 2,
+ "mzxid": 2,
+ "ctime": 1478010596964,
+ "mtime": 1478010596964,
+ "version": 0,
+ "cversion": 0,
+ "aversion": 0,
+ "ephemeralOwner": 0,
+ "dataLength": 9,
+ "numChildren": 0,
+ "pzxid": 2
+ },
+ "acls": [
+ {
+ "perms": 31,
+ "id": {
+ "scheme": "digest",
+ "id": "nifi:RuSeH3tpzgba3p9WrG/UpiSIsGg\u003d"
+ }
+ }
+ ],
+ "ephemeralOwner": 0
+ }
+]
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/test-data.json
----------------------------------------------------------------------
diff --git a/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/test-data.json b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/test-data.json
new file mode 100644
index 0000000..3d15c3b
--- /dev/null
+++ b/nifi-toolkit/nifi-toolkit-zookeeper-migrator/src/test/resources/test-data.json
@@ -0,0 +1,191 @@
+[
+ {
+ "connectString": "127.0.0.1:0",
+ "path": "/nifi"
+ },
+ {
+ "path": "/nifi",
+ "data": [
+ 115,
+ 111,
+ 109,
+ 101,
+ 32,
+ 100,
+ 97,
+ 116,
+ 97
+ ],
+ "stat": {
+ "czxid": 2,
+ "mzxid": 2,
+ "ctime": 1477602095884,
+ "mtime": 1477602095884,
+ "version": 0,
+ "cversion": 3,
+ "aversion": 0,
+ "ephemeralOwner": 0,
+ "dataLength": 9,
+ "numChildren": 3,
+ "pzxid": 6
+ },
+ "acls": [
+ {
+ "perms": 31,
+ "id": {
+ "scheme": "world",
+ "id": "anyone"
+ }
+ }
+ ],
+ "ephemeralOwner": 0
+ },
+ {
+ "path": "/nifi/1/a",
+ "data": [
+ 115,
+ 111,
+ 109,
+ 101,
+ 32,
+ 100,
+ 97,
+ 116,
+ 97
+ ],
+ "stat": {
+ "czxid": 4,
+ "mzxid": 4,
+ "ctime": 1477602095888,
+ "mtime": 1477602095888,
+ "version": 0,
+ "cversion": 0,
+ "aversion": 0,
+ "ephemeralOwner": 96836130884026368,
+ "dataLength": 9,
+ "numChildren": 0,
+ "pzxid": 4
+ },
+ "acls": [
+ {
+ "perms": 31,
+ "id": {
+ "scheme": "world",
+ "id": "anyone"
+ }
+ }
+ ],
+ "ephemeralOwner": 96836130884026368
+ },
+ {
+ "path": "/nifi/2",
+ "data": [
+ 115,
+ 111,
+ 109,
+ 101,
+ 32,
+ 100,
+ 97,
+ 116,
+ 97
+ ],
+ "stat": {
+ "czxid": 5,
+ "mzxid": 5,
+ "ctime": 1477602095889,
+ "mtime": 1477602095889,
+ "version": 0,
+ "cversion": 0,
+ "aversion": 0,
+ "ephemeralOwner": 0,
+ "dataLength": 9,
+ "numChildren": 0,
+ "pzxid": 5
+ },
+ "acls": [
+ {
+ "perms": 31,
+ "id": {
+ "scheme": "world",
+ "id": "anyone"
+ }
+ }
+ ],
+ "ephemeralOwner": 0
+ },
+ {
+ "path": "/nifi/3",
+ "data": [
+ 115,
+ 111,
+ 109,
+ 101,
+ 32,
+ 100,
+ 97,
+ 116,
+ 97
+ ],
+ "stat": {
+ "czxid": 6,
+ "mzxid": 6,
+ "ctime": 1477602095890,
+ "mtime": 1477602095890,
+ "version": 0,
+ "cversion": 0,
+ "aversion": 0,
+ "ephemeralOwner": 0,
+ "dataLength": 9,
+ "numChildren": 0,
+ "pzxid": 6
+ },
+ "acls": [
+ {
+ "perms": 31,
+ "id": {
+ "scheme": "world",
+ "id": "anyone"
+ }
+ }
+ ],
+ "ephemeralOwner": 0
+ },
+ {
+ "path": "/nifi/1",
+ "data": [
+ 115,
+ 111,
+ 109,
+ 101,
+ 32,
+ 100,
+ 97,
+ 116,
+ 97
+ ],
+ "stat": {
+ "czxid": 3,
+ "mzxid": 3,
+ "ctime": 1477602095888,
+ "mtime": 1477602095888,
+ "version": 0,
+ "cversion": 1,
+ "aversion": 0,
+ "ephemeralOwner": 0,
+ "dataLength": 9,
+ "numChildren": 1,
+ "pzxid": 4
+ },
+ "acls": [
+ {
+ "perms": 31,
+ "id": {
+ "scheme": "world",
+ "id": "anyone"
+ }
+ }
+ ],
+ "ephemeralOwner": 0
+ }
+]
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/nifi-toolkit/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-toolkit/pom.xml b/nifi-toolkit/pom.xml
index ca0f352..a898fe7 100644
--- a/nifi-toolkit/pom.xml
+++ b/nifi-toolkit/pom.xml
@@ -26,6 +26,7 @@
<module>nifi-toolkit-tls</module>
<module>nifi-toolkit-encrypt-config</module>
<module>nifi-toolkit-s2s</module>
+ <module>nifi-toolkit-zookeeper-migrator</module>
<module>nifi-toolkit-assembly</module>
</modules>
<dependencyManagement>
http://git-wip-us.apache.org/repos/asf/nifi/blob/ed6e0339/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index e10673b..0cacf93 100644
--- a/pom.xml
+++ b/pom.xml
@@ -751,6 +751,11 @@ language governing permissions and limitations under the License. -->
<version>18.0</version>
</dependency>
<dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.7</version>
+ </dependency>
+ <dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.176</version>
@@ -900,6 +905,11 @@ language governing permissions and limitations under the License. -->
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-toolkit-zookeeper-migrator</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
<artifactId>nifi-resources</artifactId>
<version>1.1.0-SNAPSHOT</version>
<classifier>resources</classifier>