From e51070b33740344b059236d67d2d08d496786179 Mon Sep 17 00:00:00 2001 From: Cedric Krumbein Date: Mon, 20 Apr 2026 17:11:17 -0700 Subject: [PATCH 1/3] ZOOKEEPER-5037: Add export and import commands to the CLI --- .../main/resources/markdown/zookeeperCLI.md | 16 ++++ .../apache/zookeeper/cli/CommandFactory.java | 2 + .../apache/zookeeper/cli/ExportCommand.java | 93 ++++++++++++++++++ .../apache/zookeeper/cli/ImportCommand.java | 95 +++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java create mode 100644 zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md b/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md index aeccf1dcfce..411f4d89f8f 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md @@ -46,11 +46,13 @@ ZooKeeper -server host:port cmd args deleteall path delquota [-n|-b|-N|-B] path exit + export path filepath get [-s] [-w] path getAcl [-s] path getAllChildrenNumber path getEphemerals path history + import path filepath listquota path ls [-s] [-w] [-R] path printwatches on|off @@ -194,8 +196,15 @@ Delete the quota under a path [zkshell: 4] delquota -N /c2 [zkshell: 5] delquota -b /c3 [zkshell: 6] delquota -B /c4 +``` + +## export +Download the contents of a znode to an external file +```bash +[zkshell: 1] export /zookeeper/config path/to/config.txt ``` + ## get Get the data of the specific path @@ -285,6 +294,13 @@ Showing the history about the recent 11 commands that you have executed 7 - history ``` +## import +Upload the contents of an external file to a znode, replacing the znode's previous contents + +```bash +[zkshell: 1] import /zookeeper/config path/to/config.txt +``` + ## listquota Listing the quota of one path diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/CommandFactory.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/CommandFactory.java index 681eabad512..bb5b740473f 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/CommandFactory.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/CommandFactory.java @@ -38,6 +38,8 @@ public enum Command { LS(LsCommand::new), GET_ACL(GetAclCommand::new), SET_ACL(SetAclCommand::new), + EXPORT(ExportCommand::new), + IMPORT(ImportCommand::new), STAT(StatCommand::new), SYNC(SyncCommand::new), SET_QUOTA(SetQuotaCommand::new), diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java new file mode 100644 index 00000000000..4f39d03a270 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java @@ -0,0 +1,93 @@ +/** + * 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.zookeeper.cli; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.data.Stat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +/** + * export command for cli + */ +public class ExportCommand extends CliCommand { + + private static Options options = new Options(); + private String args[]; + private CommandLine cl; + + static { + options.addOption("s", false, "stats"); + options.addOption("w", false, "watch"); + } + + public ExportCommand() { + super("export", "[-s] [-w] path filepath"); + } + + @Override + public CliCommand parse(String[] cmdArgs) throws CliParseException { + + CommandLineParser parser = new DefaultParser(); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } + args = cl.getArgs(); + if (args.length < 3) { + throw new CliParseException(getUsageStr()); + } + + return this; + } + + @Override + public boolean exec() throws CliException { + boolean watch = cl.hasOption("w"); + String path = args[1]; + String filepath = args[2]; + Stat stat = new Stat(); + byte data[]; + try { + data = zk.getData(path, watch, stat); + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliException(ex); + } + data = (data == null) ? "null".getBytes() : data; + try { + Files.write(Paths.get(filepath), data, StandardOpenOption.CREATE); + } catch (IOException ex) { + throw new CliException("Unable to write data to file \"" + filepath + "\"", ex); + } + if (cl.hasOption("s")) { + new StatPrinter(out).print(stat); + } + return watch; + } +} diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java new file mode 100644 index 00000000000..afe5f9187e5 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java @@ -0,0 +1,95 @@ +/** + * 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.zookeeper.cli; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.data.Stat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * import command for cli + */ +public class ImportCommand extends CliCommand { + + private static Options options = new Options(); + private String[] args; + private CommandLine cl; + + static { + options.addOption("s", false, "stats"); + options.addOption("v", true, "version"); + } + + public ImportCommand() { + super("import", "[-s] [-v version] path filepath"); + } + + @Override + public CliCommand parse(String[] cmdArgs) throws CliParseException { + CommandLineParser parser = new DefaultParser(); + try { + cl = parser.parse(options, cmdArgs); + } catch (ParseException ex) { + throw new CliParseException(ex); + } + args = cl.getArgs(); + if (args.length < 3) { + throw new CliParseException(getUsageStr()); + } + + return this; + } + + @Override + public boolean exec() throws CliException { + String path = args[1]; + String filepath = args[2]; + byte[] data; + try { + data = Files.readAllBytes(Paths.get(filepath)); + } catch (IOException ex) { + throw new CliException("Unable to read data from file \"" + filepath + "\"", ex); + } + int version; + if (cl.hasOption("v")) { + version = Integer.parseInt(cl.getOptionValue("v")); + } else { + version = -1; + } + + try { + Stat stat = zk.setData(path, data, version); + if (cl.hasOption("s")) { + new StatPrinter(out).print(stat); + } + } catch (IllegalArgumentException ex) { + throw new MalformedPathException(ex.getMessage()); + } catch (KeeperException|InterruptedException ex) { + throw new CliWrapperException(ex); + } + return false; + } +} From 47dcec0933f16ed28b8a87bf9ee39527e4ab847b Mon Sep 17 00:00:00 2001 From: Cedric Krumbein Date: Tue, 28 Apr 2026 15:12:32 -0700 Subject: [PATCH 2/3] ZOOKEEPER-5037: Incorporate feedback --- .../main/resources/markdown/zookeeperCLI.md | 16 +++++++++++++-- .../apache/zookeeper/cli/ExportCommand.java | 20 +++++++++---------- .../apache/zookeeper/cli/ImportCommand.java | 15 +++++++------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md b/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md index 411f4d89f8f..aabbee3f16f 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperCLI.md @@ -46,13 +46,13 @@ ZooKeeper -server host:port cmd args deleteall path delquota [-n|-b|-N|-B] path exit - export path filepath + export [-s] [-w] path filepath get [-s] [-w] path getAcl [-s] path getAllChildrenNumber path getEphemerals path history - import path filepath + import [-s] [-v version] path filepath listquota path ls [-s] [-w] [-R] path printwatches on|off @@ -203,6 +203,12 @@ Download the contents of a znode to an external file ```bash [zkshell: 1] export /zookeeper/config path/to/config.txt + +# -s to show the stat +[zkshell: 2] export -s /zookeeper/config path/to/config.txt + +# -w to set a watch on the data change, Notice: turn on the printwatches +[zkshell: 3] export -w /zookeeper/config path/to/config.txt ``` ## get @@ -299,6 +305,12 @@ Upload the contents of an external file to a znode, replacing the znode's previo ```bash [zkshell: 1] import /zookeeper/config path/to/config.txt + +# -s to show the stat +[zkshell: 2] import -s /zookeeper/config path/to/config.txt + +# -v to set the version of the vnode +[zkshell: 3] import -v 3 /zookeeper/config path/to/config.txt ``` ## listquota diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java index 4f39d03a270..795d3573550 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java @@ -17,6 +17,9 @@ */ package org.apache.zookeeper.cli; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -25,15 +28,10 @@ import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.data.Stat; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; - /** * export command for cli */ -public class ExportCommand extends CliCommand { +public final class ExportCommand extends CliCommand { private static Options options = new Options(); private String args[]; @@ -45,7 +43,7 @@ public class ExportCommand extends CliCommand { } public ExportCommand() { - super("export", "[-s] [-w] path filepath"); + super("export", "[-s] [-w] path filepath", options); } @Override @@ -76,14 +74,14 @@ public boolean exec() throws CliException { data = zk.getData(path, watch, stat); } catch (IllegalArgumentException ex) { throw new MalformedPathException(ex.getMessage()); - } catch (KeeperException|InterruptedException ex) { - throw new CliException(ex); + } catch (KeeperException | InterruptedException ex) { + throw new CliWrapperException(ex); } data = (data == null) ? "null".getBytes() : data; try { - Files.write(Paths.get(filepath), data, StandardOpenOption.CREATE); + Files.write(Paths.get(filepath), data); } catch (IOException ex) { - throw new CliException("Unable to write data to file \"" + filepath + "\"", ex); + throw new CliWrapperException("Unable to write data to file \"" + filepath + "\"", ex); } if (cl.hasOption("s")) { new StatPrinter(out).print(stat); diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java index afe5f9187e5..b882fadd460 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java @@ -17,6 +17,9 @@ */ package org.apache.zookeeper.cli; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -25,14 +28,10 @@ import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.data.Stat; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; - /** * import command for cli */ -public class ImportCommand extends CliCommand { +public final class ImportCommand extends CliCommand { private static Options options = new Options(); private String[] args; @@ -44,7 +43,7 @@ public class ImportCommand extends CliCommand { } public ImportCommand() { - super("import", "[-s] [-v version] path filepath"); + super("import", "[-s] [-v version] path filepath", options); } @Override @@ -71,7 +70,7 @@ public boolean exec() throws CliException { try { data = Files.readAllBytes(Paths.get(filepath)); } catch (IOException ex) { - throw new CliException("Unable to read data from file \"" + filepath + "\"", ex); + throw new CliWrapperException("Unable to read data from file \"" + filepath + "\"", ex); } int version; if (cl.hasOption("v")) { @@ -87,7 +86,7 @@ public boolean exec() throws CliException { } } catch (IllegalArgumentException ex) { throw new MalformedPathException(ex.getMessage()); - } catch (KeeperException|InterruptedException ex) { + } catch (KeeperException | InterruptedException ex) { throw new CliWrapperException(ex); } return false; From 602e6b08208e1c9dfbc31973bcb8bce1acb501e4 Mon Sep 17 00:00:00 2001 From: Cedric Krumbein Date: Tue, 28 Apr 2026 16:06:30 -0700 Subject: [PATCH 3/3] ZOOKEEPER-5037: Incorporate feedback --- .../src/main/java/org/apache/zookeeper/cli/ExportCommand.java | 2 +- .../src/main/java/org/apache/zookeeper/cli/ImportCommand.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java index 795d3573550..5255726f281 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ExportCommand.java @@ -81,7 +81,7 @@ public boolean exec() throws CliException { try { Files.write(Paths.get(filepath), data); } catch (IOException ex) { - throw new CliWrapperException("Unable to write data to file \"" + filepath + "\"", ex); + throw new CliException("Unable to write data to file \"" + filepath + "\"", ex); } if (cl.hasOption("s")) { new StatPrinter(out).print(stat); diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java index b882fadd460..ba920d03c79 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ImportCommand.java @@ -70,7 +70,7 @@ public boolean exec() throws CliException { try { data = Files.readAllBytes(Paths.get(filepath)); } catch (IOException ex) { - throw new CliWrapperException("Unable to read data from file \"" + filepath + "\"", ex); + throw new CliException("Unable to read data from file \"" + filepath + "\"", ex); } int version; if (cl.hasOption("v")) {