You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bookkeeper.apache.org by si...@apache.org on 2018/11/09 05:50:06 UTC
[bookkeeper] branch branch-4.8 updated: [TOOLS] add cookie related
commands
This is an automated email from the ASF dual-hosted git repository.
sijie pushed a commit to branch branch-4.8
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git
The following commit(s) were added to refs/heads/branch-4.8 by this push:
new 2bce677 [TOOLS] add cookie related commands
2bce677 is described below
commit 2bce677e61e797b3898f026f462445e77671ac80
Author: Sijie Guo <gu...@gmail.com>
AuthorDate: Thu Nov 8 21:47:12 2018 -0800
[TOOLS] add cookie related commands
Descriptions of the changes in this PR:
*Motivation*
In some use cases, you need cookie related tools to create/delete/update/get cookie when handling production issues.
Currently bookkeeper doesn't provide such commands.
*Changes*
Add cookie related commands
- create
- delete
- get
- update
- generate
Reviewers: Enrico Olivelli <eo...@gmail.com>, Jia Zhai <None>, Matteo Merli <mm...@apache.org>
This closes #1794 from sijie/add_cookie_commands
(cherry picked from commit e31e844505c59a9c119c6896a7f789db8f432dc2)
Signed-off-by: Sijie Guo <si...@apache.org>
---
.../apache/bookkeeper/bookie/BookieException.java | 23 +++
.../org/apache/bookkeeper/bookie/BookieShell.java | 44 ++++-
.../java/org/apache/bookkeeper/bookie/Cookie.java | 7 +-
.../bookkeeper/discover/ZKRegistrationManager.java | 5 +
.../tools/cli/commands/cookie/CookieCommand.java | 124 +++++++++++++
.../cli/commands/cookie/CreateCookieCommand.java | 102 +++++++++++
.../cli/commands/cookie/DeleteCookieCommand.java | 91 ++++++++++
.../cli/commands/cookie/GenerateCookieCommand.java | 126 +++++++++++++
.../cli/commands/cookie/GetCookieCommand.java | 101 +++++++++++
.../cli/commands/cookie/UpdateCookieCommand.java | 102 +++++++++++
.../tools/cli/commands/cookie/package-info.java | 23 +++
.../tools/cli/helpers/BookieShellCommand.java | 62 +++++++
.../tools/cli/helpers/ClientCommand.java | 2 +-
.../tools/cli/commands/CookieCommandGroup.java | 54 ++++++
....apache.bookkeeper.tools.framework.CommandGroup | 1 +
.../commands/cookie/CreateCookieCommandTest.java | 180 +++++++++++++++++++
.../commands/cookie/DeleteCookieCommandTest.java | 135 ++++++++++++++
.../commands/cookie/GenerateCookieCommandTest.java | 198 +++++++++++++++++++++
.../cli/commands/cookie/GetCookieCommandTest.java | 146 +++++++++++++++
.../commands/cookie/UpdateCookieCommandTest.java | 180 +++++++++++++++++++
.../tools/cli/helpers/BookieShellCommandTest.java | 65 +++++++
.../tools/cli/helpers/CookieCommandTestBase.java | 91 ++++++++++
.../apache/bookkeeper/tools/common/BKCommand.java | 14 ++
23 files changed, 1869 insertions(+), 7 deletions(-)
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieException.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieException.java
index 3d84148..83002ce 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieException.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieException.java
@@ -63,6 +63,8 @@ public abstract class BookieException extends Exception {
return new DiskPartitionDuplicationException();
case Code.CookieNotFoundException:
return new CookieNotFoundException();
+ case Code.CookieExistsException:
+ return new CookieExistException();
case Code.MetadataStoreException:
return new MetadataStoreException();
case Code.UnknownBookieIdException:
@@ -88,6 +90,7 @@ public abstract class BookieException extends Exception {
int MetadataStoreException = -106;
int UnknownBookieIdException = -107;
int OperationRejectedException = -108;
+ int CookieExistsException = -109;
}
public int getCode() {
@@ -118,6 +121,9 @@ public abstract class BookieException extends Exception {
case Code.CookieNotFoundException:
err = "Cookie not found";
break;
+ case Code.CookieExistsException:
+ err = "Cookie already exists";
+ break;
case Code.MetadataStoreException:
err = "Error performing metadata operations";
break;
@@ -232,6 +238,23 @@ public abstract class BookieException extends Exception {
}
/**
+ * Signal that cookie already exists when creating a new cookie.
+ */
+ public static class CookieExistException extends BookieException {
+ public CookieExistException() {
+ this("");
+ }
+
+ public CookieExistException(String reason) {
+ super(Code.CookieExistsException, reason);
+ }
+
+ public CookieExistException(Throwable cause) {
+ super(Code.CookieExistsException, cause);
+ }
+ }
+
+ /**
* Signals that an exception occurs on upgrading a bookie.
*/
public static class UpgradeException extends BookieException {
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
index 1b18055..2666007 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/BookieShell.java
@@ -90,6 +90,7 @@ import org.apache.bookkeeper.client.LedgerEntry;
import org.apache.bookkeeper.client.LedgerHandle;
import org.apache.bookkeeper.client.LedgerMetadata;
import org.apache.bookkeeper.client.UpdateLedgerOp;
+import org.apache.bookkeeper.common.annotation.InterfaceAudience.Private;
import org.apache.bookkeeper.common.util.OrderedExecutor;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.apache.bookkeeper.conf.ServerConfiguration;
@@ -111,6 +112,11 @@ import org.apache.bookkeeper.stats.NullStatsLogger;
import org.apache.bookkeeper.tools.cli.commands.bookie.LastMarkCommand;
import org.apache.bookkeeper.tools.cli.commands.bookies.ListBookiesCommand;
import org.apache.bookkeeper.tools.cli.commands.client.SimpleTestCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.CreateCookieCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.DeleteCookieCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.GenerateCookieCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.GetCookieCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.UpdateCookieCommand;
import org.apache.bookkeeper.tools.framework.CliFlags;
import org.apache.bookkeeper.util.BookKeeperConstants;
import org.apache.bookkeeper.util.DiskChecker;
@@ -186,6 +192,14 @@ public class BookieShell implements Tool {
static final String CMD_CONVERT_TO_DB_STORAGE = "convert-to-db-storage";
static final String CMD_CONVERT_TO_INTERLEAVED_STORAGE = "convert-to-interleaved-storage";
static final String CMD_REBUILD_DB_LEDGER_LOCATIONS_INDEX = "rebuild-db-ledger-locations-index";
+
+ // cookie commands
+ static final String CMD_CREATE_COOKIE = "cookie_create";
+ static final String CMD_DELETE_COOKIE = "cookie_delete";
+ static final String CMD_UPDATE_COOKIE = "cookie_update";
+ static final String CMD_GET_COOKIE = "cookie_get";
+ static final String CMD_GENERATE_COOKIE = "cookie_generate";
+
static final String CMD_HELP = "help";
final ServerConfiguration bkConf = new ServerConfiguration();
@@ -209,9 +223,15 @@ public class BookieShell implements Tool {
this.entryFormatter = entryFormatter;
}
- interface Command {
+ /**
+ * BookieShell command.
+ */
+ @Private
+ public interface Command {
int runCmd(String[] args) throws Exception;
+ String description();
+
void printUsage();
}
@@ -230,6 +250,11 @@ public class BookieShell implements Tool {
this.cmdName = cmdName;
}
+ public String description() {
+ // we used the string returned by `getUsage` as description in showing the list of commands
+ return getUsage();
+ }
+
@Override
public int runCmd(String[] args) throws Exception {
try {
@@ -2814,7 +2839,7 @@ public class BookieShell implements Tool {
}
}
- final Map<String, MyCommand> commands = new HashMap<String, MyCommand>();
+ final Map<String, Command> commands = new HashMap<>();
{
commands.put(CMD_METAFORMAT, new MetaFormatCmd());
@@ -2850,6 +2875,17 @@ public class BookieShell implements Tool {
commands.put(CMD_HELP, new HelpCmd());
commands.put(CMD_LOSTBOOKIERECOVERYDELAY, new LostBookieRecoveryDelayCmd());
commands.put(CMD_TRIGGERAUDIT, new TriggerAuditCmd());
+ // cookie related commands
+ commands.put(CMD_CREATE_COOKIE,
+ new CreateCookieCommand().asShellCommand(CMD_CREATE_COOKIE, bkConf));
+ commands.put(CMD_DELETE_COOKIE,
+ new DeleteCookieCommand().asShellCommand(CMD_DELETE_COOKIE, bkConf));
+ commands.put(CMD_UPDATE_COOKIE,
+ new UpdateCookieCommand().asShellCommand(CMD_UPDATE_COOKIE, bkConf));
+ commands.put(CMD_GET_COOKIE,
+ new GetCookieCommand().asShellCommand(CMD_GET_COOKIE, bkConf));
+ commands.put(CMD_GENERATE_COOKIE,
+ new GenerateCookieCommand().asShellCommand(CMD_GENERATE_COOKIE, bkConf));
}
@Override
@@ -2871,8 +2907,8 @@ public class BookieShell implements Tool {
+ "[-entryformat <hex/string>] [-conf configuration] <command>");
System.err.println("where command is one of:");
List<String> commandNames = new ArrayList<String>();
- for (MyCommand c : commands.values()) {
- commandNames.add(" " + c.getUsage());
+ for (Command c : commands.values()) {
+ commandNames.add(" " + c.description());
}
Collections.sort(commandNames);
for (String s : commandNames) {
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Cookie.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Cookie.java
index 4a9f6f6..7d4175c 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Cookie.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Cookie.java
@@ -212,8 +212,11 @@ public class Cookie {
public void writeToDirectory(File directory) throws IOException {
File versionFile = new File(directory,
- BookKeeperConstants.VERSION_FILENAME);
+ BookKeeperConstants.VERSION_FILENAME);
+ writeToFile(versionFile);
+ }
+ public void writeToFile (File versionFile) throws IOException {
FileOutputStream fos = new FileOutputStream(versionFile);
BufferedWriter bw = null;
try {
@@ -386,7 +389,7 @@ public class Cookie {
* Cookie builder.
*/
public static class Builder {
- private int layoutVersion = 0;
+ private int layoutVersion = CURRENT_COOKIE_LAYOUT_VERSION;
private String bookieHost = null;
private String journalDirs = null;
private String ledgerDirs = null;
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/discover/ZKRegistrationManager.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/discover/ZKRegistrationManager.java
index eab9e4f..a2a3e7c 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/discover/ZKRegistrationManager.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/discover/ZKRegistrationManager.java
@@ -37,6 +37,7 @@ import java.util.function.Function;
import lombok.extern.slf4j.Slf4j;
import org.apache.bookkeeper.bookie.BookieException;
import org.apache.bookkeeper.bookie.BookieException.BookieIllegalOpException;
+import org.apache.bookkeeper.bookie.BookieException.CookieExistException;
import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
import org.apache.bookkeeper.bookie.BookieException.MetadataStoreException;
import org.apache.bookkeeper.client.BKException;
@@ -327,6 +328,10 @@ public class ZKRegistrationManager implements RegistrationManager {
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new MetadataStoreException("Interrupted writing cookie for bookie " + bookieId, ie);
+ } catch (NoNodeException nne) {
+ throw new CookieNotFoundException(bookieId);
+ } catch (NodeExistsException nee) {
+ throw new CookieExistException(bookieId);
} catch (KeeperException e) {
throw new MetadataStoreException("Failed to write cookie for bookie " + bookieId);
}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/CookieCommand.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/CookieCommand.java
new file mode 100644
index 0000000..fe2c531
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/CookieCommand.java
@@ -0,0 +1,124 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.common.net.ServiceURI;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.apache.bookkeeper.meta.exceptions.MetadataException;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.apache.bookkeeper.tools.cli.helpers.BookieShellCommand;
+import org.apache.bookkeeper.tools.common.BKCommand;
+import org.apache.bookkeeper.tools.common.BKFlags;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.bookkeeper.tools.framework.CliSpec;
+import org.apache.commons.configuration.CompositeConfiguration;
+
+/**
+ * This is a mixin for cookie related commands to extends.
+ */
+@Slf4j
+abstract class CookieCommand<CookieFlagsT extends CliFlags>
+ extends BKCommand<CookieFlagsT> {
+
+ protected CookieCommand(CliSpec<CookieFlagsT> spec) {
+ super(spec);
+ }
+
+ @Override
+ protected boolean apply(ServiceURI serviceURI,
+ CompositeConfiguration conf,
+ BKFlags globalFlags,
+ CookieFlagsT cmdFlags) {
+ ServerConfiguration serverConf = new ServerConfiguration();
+ serverConf.loadConf(conf);
+
+ if (null != serviceURI) {
+ serverConf.setMetadataServiceUri(serviceURI.getUri().toString());
+ }
+
+ try {
+ return MetadataDrivers.runFunctionWithRegistrationManager(serverConf, registrationManager -> {
+ try {
+ apply(registrationManager, cmdFlags);
+ return true;
+ } catch (Exception e) {
+ throw new UncheckedExecutionException(e);
+ }
+ });
+ } catch (MetadataException | ExecutionException | UncheckedExecutionException e) {
+ Throwable cause = e;
+ if (!(e instanceof MetadataException) && null != e.getCause()) {
+ cause = e.getCause();
+ }
+ spec.console().println("Failed to process cookie command '" + name() + "'");
+ cause.printStackTrace(spec.console());
+ return false;
+ }
+ }
+
+ protected String getBookieId(CookieFlagsT cmdFlags) throws UnknownHostException {
+ checkArgument(
+ cmdFlags.arguments.size() == 1,
+ "No bookie id or more bookie ids is specified");
+
+ String bookieId = cmdFlags.arguments.get(0);
+ try {
+ new BookieSocketAddress(bookieId);
+ } catch (UnknownHostException nhe) {
+ spec.console()
+ .println("Invalid bookie id '"
+ + bookieId + "'is used to create cookie."
+ + " Bookie id should be in the format of '<hostname>:<port>'");
+ throw nhe;
+ }
+ return bookieId;
+ }
+
+ protected byte[] readCookieDataFromFile(String cookieFile) throws IOException {
+ try {
+ return Files.readAllBytes(Paths.get(cookieFile));
+ } catch (NoSuchFileException nfe) {
+ spec.console()
+ .println("Cookie file '" + cookieFile + "' doesn't exist.");
+ throw nfe;
+ }
+ }
+
+
+ protected abstract void apply(RegistrationManager rm, CookieFlagsT cmdFlags)
+ throws Exception;
+
+ public org.apache.bookkeeper.bookie.BookieShell.Command asShellCommand(String shellCmdName,
+ CompositeConfiguration conf) {
+ return new BookieShellCommand<>(shellCmdName, this, conf);
+ }
+}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/CreateCookieCommand.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/CreateCookieCommand.java
new file mode 100644
index 0000000..430fed9
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/CreateCookieCommand.java
@@ -0,0 +1,102 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import com.beust.jcommander.Parameter;
+import java.io.PrintStream;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.bookie.BookieException;
+import org.apache.bookkeeper.bookie.BookieException.CookieExistException;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.tools.cli.commands.cookie.CreateCookieCommand.Flags;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.bookkeeper.tools.framework.CliSpec;
+import org.apache.bookkeeper.versioning.Version;
+import org.apache.bookkeeper.versioning.Versioned;
+
+/**
+ * A command that create cookie.
+ */
+@Slf4j
+public class CreateCookieCommand extends CookieCommand<Flags> {
+
+ private static final String NAME = "create";
+ private static final String DESC = "Create a cookie for a given bookie";
+
+ /**
+ * Flags to create a cookie for a given bookie.
+ */
+ @Accessors(fluent = true)
+ @Setter
+ public static class Flags extends CliFlags {
+
+ @Parameter(
+ names = { "-cf", "--cookie-file" },
+ description = "The file to be uploaded as cookie",
+ required = true)
+ private String cookieFile;
+
+ }
+
+ public CreateCookieCommand() {
+ this(new Flags());
+ }
+
+ protected CreateCookieCommand(PrintStream console) {
+ this(new Flags(), console);
+ }
+
+ public CreateCookieCommand(Flags flags) {
+ this(flags, System.out);
+ }
+
+ private CreateCookieCommand(Flags flags, PrintStream console) {
+ super(CliSpec.<Flags>newBuilder()
+ .withName(NAME)
+ .withDescription(DESC)
+ .withFlags(flags)
+ .withConsole(console)
+ .withArgumentsUsage("<bookie-id>")
+ .build());
+ }
+
+ @Override
+ protected void apply(RegistrationManager rm, Flags cmdFlags) throws Exception {
+ String bookieId = getBookieId(cmdFlags);
+
+ byte[] data = readCookieDataFromFile(cmdFlags.cookieFile);
+ Versioned<byte[]> cookie = new Versioned<>(data, Version.NEW);
+ try {
+ rm.writeCookie(bookieId, cookie);
+ } catch (CookieExistException cee) {
+ spec.console()
+ .println("Cookie already exist for bookie '" + bookieId + "'");
+ throw cee;
+ } catch (BookieException be) {
+ spec.console()
+ .println("Exception on creating cookie for bookie '" + bookieId + "'");
+ be.printStackTrace(spec.console());
+ throw be;
+ }
+ }
+
+}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/DeleteCookieCommand.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/DeleteCookieCommand.java
new file mode 100644
index 0000000..4c42615
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/DeleteCookieCommand.java
@@ -0,0 +1,91 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import java.io.PrintStream;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.bookie.BookieException;
+import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.tools.cli.commands.cookie.DeleteCookieCommand.Flags;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.bookkeeper.tools.framework.CliSpec;
+import org.apache.bookkeeper.versioning.LongVersion;
+
+/**
+ * A command that deletes cookie.
+ */
+@Slf4j
+public class DeleteCookieCommand extends CookieCommand<Flags> {
+
+ private static final String NAME = "delete";
+ private static final String DESC = "Delete a cookie for a given bookie";
+
+ /**
+ * Flags to delete a cookie for a given bookie.
+ */
+ @Accessors(fluent = true)
+ @Setter
+ public static class Flags extends CliFlags {
+ }
+
+ public DeleteCookieCommand() {
+ this(new Flags());
+ }
+
+ DeleteCookieCommand(PrintStream console) {
+ this(new Flags(), console);
+ }
+
+ public DeleteCookieCommand(Flags flags) {
+ this(flags, System.out);
+ }
+
+ private DeleteCookieCommand(Flags flags, PrintStream console) {
+ super(CliSpec.<Flags>newBuilder()
+ .withName(NAME)
+ .withDescription(DESC)
+ .withFlags(flags)
+ .withConsole(console)
+ .withArgumentsUsage("<bookie-id>")
+ .build());
+ }
+
+ @Override
+ protected void apply(RegistrationManager rm, Flags cmdFlags) throws Exception {
+ String bookieId = getBookieId(cmdFlags);
+
+ try {
+ rm.removeCookie(bookieId, new LongVersion(-1));
+ } catch (CookieNotFoundException cee) {
+ spec.console()
+ .println("Cookie not found for bookie '" + bookieId + "'");
+ throw cee;
+ } catch (BookieException be) {
+ spec.console()
+ .println("Exception on deleting cookie for bookie '" + bookieId + "'");
+ be.printStackTrace(spec.console());
+ throw be;
+ }
+ }
+
+}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/GenerateCookieCommand.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/GenerateCookieCommand.java
new file mode 100644
index 0000000..daa3c43
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/GenerateCookieCommand.java
@@ -0,0 +1,126 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import com.beust.jcommander.Parameter;
+import java.io.File;
+import java.io.PrintStream;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.bookie.Cookie;
+import org.apache.bookkeeper.bookie.Cookie.Builder;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.tools.cli.commands.cookie.GenerateCookieCommand.Flags;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.bookkeeper.tools.framework.CliSpec;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * A command that generate cookie.
+ */
+@Slf4j
+public class GenerateCookieCommand extends CookieCommand<Flags> {
+
+ private static final String NAME = "generate";
+ private static final String DESC = "Generate a cookie for a given bookie";
+
+ /**
+ * Flags to generate a cookie for a given bookie.
+ */
+ @Accessors(fluent = true)
+ @Setter
+ public static class Flags extends CliFlags {
+
+ @Parameter(
+ names = { "-j", "--journal-dirs" },
+ description = "The journal directories used by this bookie",
+ required = true)
+ private String journalDirs;
+
+ @Parameter(
+ names = { "-l", "--ledger-dirs" },
+ description = "The ledger directories used by this bookie",
+ required = true)
+ private String ledgerDirs;
+
+ @Parameter(
+ names = { "-i", "--instance-id" },
+ description = "The instance id of the cluster that this bookie belongs to."
+ + " If omitted, it will used the instance id of the cluster that this cli connects to.")
+ private String instanceId = null;
+
+ @Parameter(
+ names = { "-o", "--output-file" },
+ description = "The output file to save the generated cookie.",
+ required = true)
+ private String outputFile;
+
+ }
+
+ public GenerateCookieCommand() {
+ this(new Flags());
+ }
+
+ GenerateCookieCommand(PrintStream console) {
+ this(new Flags(), console);
+ }
+
+ public GenerateCookieCommand(Flags flags) {
+ this(flags, System.out);
+ }
+
+ private GenerateCookieCommand(Flags flags, PrintStream console) {
+ super(CliSpec.<Flags>newBuilder()
+ .withName(NAME)
+ .withDescription(DESC)
+ .withFlags(flags)
+ .withConsole(console)
+ .withArgumentsUsage("<bookie-id>")
+ .build());
+ }
+
+ @Override
+ protected void apply(RegistrationManager rm, Flags cmdFlags) throws Exception {
+ String bookieId = getBookieId(cmdFlags);
+
+ String instanceId;
+ if (null == cmdFlags.instanceId) {
+ instanceId = rm.getClusterInstanceId();
+ } else {
+ instanceId = cmdFlags.instanceId;
+ }
+
+ Builder builder = Cookie.newBuilder();
+ builder.setBookieHost(bookieId);
+ if (StringUtils.isEmpty(instanceId)) {
+ builder.setInstanceId(null);
+ } else {
+ builder.setInstanceId(instanceId);
+ }
+ builder.setJournalDirs(cmdFlags.journalDirs);
+ builder.setLedgerDirs(cmdFlags.ledgerDirs);
+
+ Cookie cookie = builder.build();
+ cookie.writeToFile(new File(cmdFlags.outputFile));
+ spec.console().println("Successfully saved the generated cookie to " + cmdFlags.outputFile);
+ }
+
+}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/GetCookieCommand.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/GetCookieCommand.java
new file mode 100644
index 0000000..76f5f7c
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/GetCookieCommand.java
@@ -0,0 +1,101 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import java.io.PrintStream;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.bookie.BookieException;
+import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
+import org.apache.bookkeeper.bookie.Cookie;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.net.BookieSocketAddress;
+import org.apache.bookkeeper.tools.cli.commands.cookie.GetCookieCommand.Flags;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.bookkeeper.tools.framework.CliSpec;
+import org.apache.bookkeeper.versioning.Versioned;
+
+/**
+ * A command that deletes cookie.
+ */
+@Slf4j
+public class GetCookieCommand extends CookieCommand<Flags> {
+
+ private static final String NAME = "get";
+ private static final String DESC = "Retrieve a cookie for a given bookie";
+
+ /**
+ * Flags to delete a cookie for a given bookie.
+ */
+ @Accessors(fluent = true)
+ @Setter
+ public static class Flags extends CliFlags {
+ }
+
+ public GetCookieCommand() {
+ this(new Flags());
+ }
+
+ GetCookieCommand(PrintStream console) {
+ this(new Flags(), console);
+ }
+
+ public GetCookieCommand(Flags flags) {
+ this(flags, System.out);
+ }
+
+ private GetCookieCommand(Flags flags, PrintStream console) {
+ super(CliSpec.<Flags>newBuilder()
+ .withName(NAME)
+ .withDescription(DESC)
+ .withFlags(flags)
+ .withConsole(console)
+ .withArgumentsUsage("<bookie-id>")
+ .build());
+ }
+
+ @Override
+ protected void apply(RegistrationManager rm, Flags cmdFlags) throws Exception {
+ String bookieId = getBookieId(cmdFlags);
+
+ try {
+ Versioned<Cookie> cookie = Cookie.readFromRegistrationManager(
+ rm, new BookieSocketAddress(bookieId)
+ );
+ spec.console().println("Cookie for bookie '" + bookieId + "' is:");
+ spec.console().println("---");
+ spec.console().println(
+ cookie.getValue()
+ );
+ spec.console().println("---");
+ } catch (CookieNotFoundException cee) {
+ spec.console()
+ .println("Cookie not found for bookie '" + bookieId + "'");
+ throw cee;
+ } catch (BookieException be) {
+ spec.console()
+ .println("Exception on getting cookie for bookie '" + bookieId + "'");
+ be.printStackTrace(spec.console());
+ throw be;
+ }
+ }
+
+}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/UpdateCookieCommand.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/UpdateCookieCommand.java
new file mode 100644
index 0000000..77e5f05
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/UpdateCookieCommand.java
@@ -0,0 +1,102 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import com.beust.jcommander.Parameter;
+import java.io.PrintStream;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.bookie.BookieException;
+import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.tools.cli.commands.cookie.UpdateCookieCommand.Flags;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.bookkeeper.tools.framework.CliSpec;
+import org.apache.bookkeeper.versioning.LongVersion;
+import org.apache.bookkeeper.versioning.Versioned;
+
+/**
+ * A command that updates cookie.
+ */
+@Slf4j
+public class UpdateCookieCommand extends CookieCommand<Flags> {
+
+ private static final String NAME = "update";
+ private static final String DESC = "Update a cookie for a given bookie";
+
+ /**
+ * Flags to create a cookie for a given bookie.
+ */
+ @Accessors(fluent = true)
+ @Setter
+ public static class Flags extends CliFlags {
+
+ @Parameter(
+ names = { "-cf", "--cookie-file" },
+ description = "The file to be uploaded as cookie",
+ required = true)
+ private String cookieFile;
+
+ }
+
+ public UpdateCookieCommand() {
+ this(new Flags());
+ }
+
+ UpdateCookieCommand(PrintStream console) {
+ this(new Flags(), console);
+ }
+
+ public UpdateCookieCommand(Flags flags) {
+ this(flags, System.out);
+ }
+
+ private UpdateCookieCommand(Flags flags, PrintStream console) {
+ super(CliSpec.<Flags>newBuilder()
+ .withName(NAME)
+ .withDescription(DESC)
+ .withFlags(flags)
+ .withConsole(console)
+ .withArgumentsUsage("<bookie-id>")
+ .build());
+ }
+
+ @Override
+ protected void apply(RegistrationManager rm, Flags cmdFlags) throws Exception {
+ String bookieId = getBookieId(cmdFlags);
+
+ byte[] data = readCookieDataFromFile(cmdFlags.cookieFile);
+ Versioned<byte[]> cookie = new Versioned<>(data, new LongVersion(-1L));
+ try {
+ rm.writeCookie(bookieId, cookie);
+ } catch (CookieNotFoundException cnfe) {
+ spec.console()
+ .println("Cookie not found for bookie '" + bookieId + "' to update");
+ throw cnfe;
+ } catch (BookieException be) {
+ spec.console()
+ .println("Exception on updating cookie for bookie '" + bookieId + "'");
+ be.printStackTrace(spec.console());
+ throw be;
+ }
+ }
+
+}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/package-info.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/package-info.java
new file mode 100644
index 0000000..248c49a
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/commands/cookie/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Cookie related cli commands.
+ */
+package org.apache.bookkeeper.tools.cli.commands.cookie;
\ No newline at end of file
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/helpers/BookieShellCommand.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/helpers/BookieShellCommand.java
new file mode 100644
index 0000000..4010e60
--- /dev/null
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/helpers/BookieShellCommand.java
@@ -0,0 +1,62 @@
+/*
+ * 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.bookkeeper.tools.cli.helpers;
+
+import org.apache.bookkeeper.bookie.BookieShell.Command;
+import org.apache.bookkeeper.tools.common.BKCommand;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.commons.configuration.CompositeConfiguration;
+
+/**
+ * This is a util class that converts new cli command to old shell command.
+ */
+public class BookieShellCommand<CliFlagsT extends CliFlags> implements Command {
+
+ protected final String shellCmdName;
+ protected final BKCommand<CliFlagsT> bkCmd;
+ protected final CompositeConfiguration conf;
+
+ public BookieShellCommand(String shellCmdName,
+ BKCommand<CliFlagsT> bkCmd,
+ CompositeConfiguration conf) {
+ this.shellCmdName = shellCmdName;
+ this.bkCmd = bkCmd;
+ this.conf = conf;
+ }
+
+ @Override
+ public int runCmd(String[] args) throws Exception {
+ return bkCmd.apply(
+ shellCmdName,
+ conf,
+ args
+ );
+ }
+
+ @Override
+ public String description() {
+ return shellCmdName + " [options]";
+ }
+
+ @Override
+ public void printUsage() {
+ bkCmd.usage();
+ }
+}
diff --git a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/helpers/ClientCommand.java b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/helpers/ClientCommand.java
index ae807df..45cb40e 100644
--- a/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/helpers/ClientCommand.java
+++ b/bookkeeper-server/src/main/java/org/apache/bookkeeper/tools/cli/helpers/ClientCommand.java
@@ -66,7 +66,7 @@ public abstract class ClientCommand<ClientFlagsT extends CliFlags> extends BKCom
run(bk, cmdFlags);
return true;
} catch (Exception e) {
- log.error("Faild to process command '{}'", name(), e);
+ log.error("Failed to process command '{}'", name(), e);
return false;
}
}
diff --git a/tools/all/src/main/java/org/apache/bookkeeper/tools/cli/commands/CookieCommandGroup.java b/tools/all/src/main/java/org/apache/bookkeeper/tools/cli/commands/CookieCommandGroup.java
new file mode 100644
index 0000000..3dded4e
--- /dev/null
+++ b/tools/all/src/main/java/org/apache/bookkeeper/tools/cli/commands/CookieCommandGroup.java
@@ -0,0 +1,54 @@
+/*
+ * 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.bookkeeper.tools.cli.commands;
+
+import org.apache.bookkeeper.tools.cli.BKCtl;
+import org.apache.bookkeeper.tools.cli.commands.cookie.CreateCookieCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.DeleteCookieCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.GenerateCookieCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.GetCookieCommand;
+import org.apache.bookkeeper.tools.cli.commands.cookie.UpdateCookieCommand;
+import org.apache.bookkeeper.tools.common.BKFlags;
+import org.apache.bookkeeper.tools.framework.CliCommandGroup;
+import org.apache.bookkeeper.tools.framework.CliSpec;
+
+/**
+ * Commands that operates cookies.
+ */
+public class CookieCommandGroup extends CliCommandGroup<BKFlags> {
+
+ private static final String NAME = "cookie";
+ private static final String DESC = "Commands on operating cookies";
+
+ private static final CliSpec<BKFlags> spec = CliSpec.<BKFlags>newBuilder()
+ .withName(NAME)
+ .withDescription(DESC)
+ .withParent(BKCtl.NAME)
+ .addCommand(new CreateCookieCommand())
+ .addCommand(new DeleteCookieCommand())
+ .addCommand(new GetCookieCommand())
+ .addCommand(new UpdateCookieCommand())
+ .addCommand(new GenerateCookieCommand())
+ .build();
+
+ public CookieCommandGroup() {
+ super(spec);
+ }
+}
diff --git a/tools/all/src/main/resources/META-INF/services/org.apache.bookkeeper.tools.framework.CommandGroup b/tools/all/src/main/resources/META-INF/services/org.apache.bookkeeper.tools.framework.CommandGroup
index 44fc194..0147eca 100644
--- a/tools/all/src/main/resources/META-INF/services/org.apache.bookkeeper.tools.framework.CommandGroup
+++ b/tools/all/src/main/resources/META-INF/services/org.apache.bookkeeper.tools.framework.CommandGroup
@@ -18,6 +18,7 @@
org.apache.bookkeeper.tools.cli.commands.BookieCommandGroup
org.apache.bookkeeper.tools.cli.commands.BookiesCommandGroup
+org.apache.bookkeeper.tools.cli.commands.CookieCommandGroup
org.apache.bookkeeper.tools.cli.commands.LedgerCommandGroup
org.apache.bookkeeper.stream.cli.ClusterCommandGroup
org.apache.bookkeeper.stream.cli.NamespaceCommandGroup
diff --git a/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/CreateCookieCommandTest.java b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/CreateCookieCommandTest.java
new file mode 100644
index 0000000..fcfae0b
--- /dev/null
+++ b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/CreateCookieCommandTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import org.apache.bookkeeper.bookie.BookieException.CookieExistException;
+import org.apache.bookkeeper.bookie.BookieException.OperationRejectedException;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.apache.bookkeeper.tools.cli.helpers.CookieCommandTestBase;
+import org.apache.bookkeeper.tools.common.BKFlags;
+import org.apache.bookkeeper.versioning.Versioned;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * Unit test {@link CreateCookieCommand}.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ MetadataDrivers.class })
+public class CreateCookieCommandTest extends CookieCommandTestBase {
+
+ @Rule
+ public final TemporaryFolder testFolder = new TemporaryFolder();
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ private final PrintStream console = new PrintStream(output);
+
+ private boolean runCommand(String[] args) {
+ CreateCookieCommand createCmd = new CreateCookieCommand(console);
+ BKFlags bkFlags = new BKFlags();
+ bkFlags.serviceUri = "zk://127.0.0.1";
+ return createCmd.apply(bkFlags, args);
+ }
+
+ private String getConsoleOutput() {
+ return new String(output.toByteArray(), UTF_8);
+ }
+
+ /**
+ * Run a command without providing bookie id.
+ */
+ @Test
+ public void testMissingBookieId() {
+ assertFalse(runCommand(new String[] {}));
+ String consoleOutput = getConsoleOutput();
+ assertBookieIdMissing(consoleOutput);
+ }
+
+ private void assertPrintUsage(String consoleOutput) {
+ assertPrintUsage(consoleOutput, "create [flags] <bookie-id>");
+ }
+
+ /**
+ * Run a command without cookie file.
+ */
+ @Test
+ public void testMissingCookieFileOption() {
+ assertFalse(runCommand(new String[] { BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertOptionMissing(consoleOutput, "-cf, --cookie-file");
+ assertPrintUsage(consoleOutput);
+ }
+
+ /**
+ * Run a command with invalid bookie id.
+ */
+ @Test
+ public void testInvalidBookieId() {
+ assertFalse(runCommand(new String[] { "-cf", "test-cookie-file", INVALID_BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertInvalidBookieId(consoleOutput, INVALID_BOOKIE_ID);
+ }
+
+ /**
+ * Run a command with a non-existent cookie file.
+ */
+ @Test
+ public void testCreateCookieFromNonExistentCookieFile() {
+ String file = "/path/to/non-existent-cookie-file";
+ assertFalse(runCommand(new String[] { "-cf", file, BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertCookieFileNotExists(consoleOutput, file);
+ }
+
+ /**
+ * A successful run.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testCreateCookieFromExistentCookieFile() throws Exception {
+ File file = testFolder.newFile("test-cookie-file");
+ byte[] content = "test-create-cookie".getBytes(UTF_8);
+ Files.write(Paths.get(file.toURI()), content);
+ String fileName = file.getPath();
+ assertTrue(runCommand(new String[] { "-cf", fileName, BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(consoleOutput, consoleOutput.isEmpty());
+ verify(rm, times(1)).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+ }
+
+ /**
+ * Run a command to create cookie on an existing cookie.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testCreateAlreadyExistedCookie() throws Exception {
+ doThrow(new CookieExistException())
+ .when(rm).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+
+ File file = testFolder.newFile("test-cookie-file");
+ byte[] content = "test-create-cookie".getBytes(UTF_8);
+ Files.write(Paths.get(file.toURI()), content);
+ String fileName = file.getPath();
+ assertFalse(runCommand(new String[] { "-cf", fileName, BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Cookie already exist for bookie '" + BOOKIE_ID + "'"));
+ verify(rm, times(1)).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+ }
+
+ /**
+ * Run a command to create cookie when exception is thrown.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testCreateCookieException() throws Exception {
+ doThrow(new OperationRejectedException())
+ .when(rm).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+
+ File file = testFolder.newFile("test-cookie-file");
+ byte[] content = "test-create-cookie".getBytes(UTF_8);
+ Files.write(Paths.get(file.toURI()), content);
+ String fileName = file.getPath();
+ assertFalse(runCommand(new String[] { "-cf", fileName, BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Exception on creating cookie for bookie '" + BOOKIE_ID + "'"));
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains(OperationRejectedException.class.getName()));
+ verify(rm, times(1)).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+ }
+
+}
diff --git a/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/DeleteCookieCommandTest.java b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/DeleteCookieCommandTest.java
new file mode 100644
index 0000000..01ad58d
--- /dev/null
+++ b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/DeleteCookieCommandTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
+import org.apache.bookkeeper.bookie.BookieException.OperationRejectedException;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.apache.bookkeeper.tools.cli.helpers.CookieCommandTestBase;
+import org.apache.bookkeeper.tools.common.BKFlags;
+import org.apache.bookkeeper.versioning.LongVersion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * Unit test {@link DeleteCookieCommand}.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ MetadataDrivers.class })
+public class DeleteCookieCommandTest extends CookieCommandTestBase {
+
+ @Rule
+ public final TemporaryFolder testFolder = new TemporaryFolder();
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ private final PrintStream console = new PrintStream(output);
+
+ private boolean runCommand(String[] args) {
+ DeleteCookieCommand deleteCmd = new DeleteCookieCommand(console);
+ BKFlags bkFlags = new BKFlags();
+ bkFlags.serviceUri = "zk://127.0.0.1";
+ return deleteCmd.apply(bkFlags, args);
+ }
+
+ private String getConsoleOutput() {
+ return new String(output.toByteArray(), UTF_8);
+ }
+
+ /**
+ * Run a command without providing bookie id.
+ */
+ @Test
+ public void testMissingBookieId() {
+ assertFalse(runCommand(new String[] {}));
+ String consoleOutput = getConsoleOutput();
+ assertBookieIdMissing(consoleOutput);
+ }
+
+ /**
+ * Run a command with invalid bookie id.
+ */
+ @Test
+ public void testInvalidBookieId() {
+ assertFalse(runCommand(new String[] { INVALID_BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertInvalidBookieId(consoleOutput, INVALID_BOOKIE_ID);
+ }
+
+ /**
+ * A successful run.
+ */
+ @Test
+ public void testDeleteCookieFromExistentCookieFile() throws Exception {
+ assertTrue(runCommand(new String[] { BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(consoleOutput, consoleOutput.isEmpty());
+ verify(rm, times(1)).removeCookie(eq(BOOKIE_ID), eq(new LongVersion(-1L)));
+ }
+
+ /**
+ * Run a command to delete cookie on an non-existent cookie.
+ */
+ @Test
+ public void testDeleteNonExistedCookie() throws Exception {
+ doThrow(new CookieNotFoundException())
+ .when(rm).removeCookie(eq(BOOKIE_ID), eq(new LongVersion(-1L)));
+
+ assertFalse(runCommand(new String[] { BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Cookie not found for bookie '" + BOOKIE_ID + "'"));
+ verify(rm, times(1)).removeCookie(eq(BOOKIE_ID), eq(new LongVersion(-1L)));
+ }
+
+ /**
+ * Run a command to delete cookie when exception is thrown.
+ */
+ @Test
+ public void testDeleteCookieException() throws Exception {
+ doThrow(new OperationRejectedException())
+ .when(rm).removeCookie(eq(BOOKIE_ID), eq(new LongVersion(-1L)));
+
+ assertFalse(runCommand(new String[] { BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Exception on deleting cookie for bookie '" + BOOKIE_ID + "'"));
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains(OperationRejectedException.class.getName()));
+ verify(rm, times(1)).removeCookie(eq(BOOKIE_ID), eq(new LongVersion(-1L)));
+ }
+
+}
diff --git a/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/GenerateCookieCommandTest.java b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/GenerateCookieCommandTest.java
new file mode 100644
index 0000000..6e27294
--- /dev/null
+++ b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/GenerateCookieCommandTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import org.apache.bookkeeper.bookie.Cookie;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.apache.bookkeeper.tools.cli.helpers.CookieCommandTestBase;
+import org.apache.bookkeeper.tools.common.BKFlags;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * Unit test {@link GetCookieCommand}.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ MetadataDrivers.class })
+public class GenerateCookieCommandTest extends CookieCommandTestBase {
+
+ @Rule
+ public final TemporaryFolder testFolder = new TemporaryFolder();
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ private final PrintStream console = new PrintStream(output);
+
+ private boolean runCommand(String[] args) {
+ GenerateCookieCommand getCmd = new GenerateCookieCommand(console);
+ BKFlags bkFlags = new BKFlags();
+ bkFlags.serviceUri = "zk://127.0.0.1";
+ return getCmd.apply(bkFlags, args);
+ }
+
+ private String getConsoleOutput() {
+ return new String(output.toByteArray(), UTF_8);
+ }
+
+ /**
+ * Run a command without providing bookie id.
+ */
+ @Test
+ public void testMissingBookieId() {
+ assertFalse(runCommand(new String[] {}));
+ String consoleOutput = getConsoleOutput();
+ assertBookieIdMissing(consoleOutput);
+ }
+
+ /**
+ * Run a command with invalid bookie id.
+ */
+ @Test
+ public void testInvalidBookieId() {
+ assertFalse(runCommand(new String[] {
+ "-j", "/path/to/journal",
+ "-l", "/path/to/ledgers",
+ "-o", "/path/to/cookie-file",
+ INVALID_BOOKIE_ID
+ }));
+ String consoleOutput = getConsoleOutput();
+ assertInvalidBookieId(consoleOutput, INVALID_BOOKIE_ID);
+ }
+
+ /**
+ * Run a command without journal dirs.
+ */
+ @Test
+ public void testMissingJournalDir() {
+ assertFalse(runCommand(new String[] { "-l", "/path/to/ledgers", "-o", "/path/to/cookie-file", BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertOptionMissing(consoleOutput, "-j, --journal-dirs");
+ }
+
+ /**
+ * Run a command without ledger dirs.
+ */
+ @Test
+ public void testMissingLedgerDirs() {
+ assertFalse(runCommand(new String[] { "-j", "/path/to/journal", "-o", "/path/to/cookie-file", BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertOptionMissing(consoleOutput, "-l, --ledger-dirs");
+ }
+
+ /**
+ * Run a command without output file.
+ */
+ @Test
+ public void testMissingOutputFile() {
+ assertFalse(runCommand(new String[] { "-j", "/path/to/journal", "-l", "/path/to/ledgers", BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertOptionMissing(consoleOutput, "-o, --output-file");
+ }
+
+ /**
+ * A successful run without instance id.
+ */
+ @Test
+ public void testGenerateCookieWithoutInstanceId() throws Exception {
+ File cookieFile = testFolder.newFile("cookie-without-instance-id");
+ String journalDir = "/path/to/journal";
+ String ledgersDir = "/path/to/ledgers";
+ String instanceId = "test-instance-id";
+
+ Cookie cookie = Cookie.newBuilder()
+ .setBookieHost(BOOKIE_ID)
+ .setInstanceId(instanceId)
+ .setJournalDirs(journalDir)
+ .setLedgerDirs(ledgersDir)
+ .build();
+
+ when(rm.getClusterInstanceId()).thenReturn(instanceId);
+ assertTrue(
+ getConsoleOutput(),
+ runCommand(new String[] {
+ "-l", ledgersDir,
+ "-j", journalDir,
+ "-o", cookieFile.getPath(),
+ BOOKIE_ID
+ }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(consoleOutput, consoleOutput.contains(
+ "Successfully saved the generated cookie to " + cookieFile.getPath()
+ ));
+ verify(rm, times(1)).getClusterInstanceId();
+
+ byte[] data = Files.readAllBytes(Paths.get(cookieFile.getPath()));
+ assertArrayEquals(cookie.toString().getBytes(UTF_8), data);
+ }
+
+ /**
+ * A successful run with instance id.
+ */
+ @Test
+ public void testGenerateCookieWithInstanceId() throws Exception {
+ File cookieFile = testFolder.newFile("cookie-with-instance-id");
+ String journalDir = "/path/to/journal";
+ String ledgersDir = "/path/to/ledgers";
+ String instanceId = "test-instance-id";
+
+ Cookie cookie = Cookie.newBuilder()
+ .setBookieHost(BOOKIE_ID)
+ .setInstanceId(instanceId)
+ .setJournalDirs(journalDir)
+ .setLedgerDirs(ledgersDir)
+ .build();
+
+ when(rm.getClusterInstanceId()).thenReturn(instanceId);
+ assertTrue(
+ getConsoleOutput(),
+ runCommand(new String[] {
+ "-l", ledgersDir,
+ "-j", journalDir,
+ "-o", cookieFile.getPath(),
+ "-i", instanceId,
+ BOOKIE_ID
+ }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(consoleOutput, consoleOutput.contains(
+ "Successfully saved the generated cookie to " + cookieFile.getPath()
+ ));
+ verify(rm, times(0)).getClusterInstanceId();
+
+ byte[] data = Files.readAllBytes(Paths.get(cookieFile.getPath()));
+ assertArrayEquals(cookie.toString().getBytes(UTF_8), data);
+ }
+
+}
diff --git a/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/GetCookieCommandTest.java b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/GetCookieCommandTest.java
new file mode 100644
index 0000000..ef45a05
--- /dev/null
+++ b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/GetCookieCommandTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
+import org.apache.bookkeeper.bookie.BookieException.OperationRejectedException;
+import org.apache.bookkeeper.bookie.Cookie;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.apache.bookkeeper.tools.cli.helpers.CookieCommandTestBase;
+import org.apache.bookkeeper.tools.common.BKFlags;
+import org.apache.bookkeeper.versioning.LongVersion;
+import org.apache.bookkeeper.versioning.Versioned;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * Unit test {@link GetCookieCommand}.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ MetadataDrivers.class })
+public class GetCookieCommandTest extends CookieCommandTestBase {
+
+ @Rule
+ public final TemporaryFolder testFolder = new TemporaryFolder();
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ private final PrintStream console = new PrintStream(output);
+
+ private boolean runCommand(String[] args) {
+ GetCookieCommand getCmd = new GetCookieCommand(console);
+ BKFlags bkFlags = new BKFlags();
+ bkFlags.serviceUri = "zk://127.0.0.1";
+ return getCmd.apply(bkFlags, args);
+ }
+
+ private String getConsoleOutput() {
+ return new String(output.toByteArray(), UTF_8);
+ }
+
+ /**
+ * Run a command without providing bookie id.
+ */
+ @Test
+ public void testMissingBookieId() {
+ assertFalse(runCommand(new String[] {}));
+ String consoleOutput = getConsoleOutput();
+ assertBookieIdMissing(consoleOutput);
+ }
+
+ /**
+ * Run a command with invalid bookie id.
+ */
+ @Test
+ public void testInvalidBookieId() {
+ assertFalse(runCommand(new String[] { INVALID_BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertInvalidBookieId(consoleOutput, INVALID_BOOKIE_ID);
+ }
+
+ /**
+ * A successful run.
+ */
+ @Test
+ public void testGetCookieFromExistentCookieFile() throws Exception {
+ Cookie cookie = Cookie.newBuilder()
+ .setBookieHost(BOOKIE_ID)
+ .setInstanceId("test-instance-id")
+ .setJournalDirs("/path/to/journal/dir")
+ .setLedgerDirs("/path/to/ledger/dirs")
+ .build();
+ when(rm.readCookie(eq(BOOKIE_ID)))
+ .thenReturn(new Versioned<>(cookie.toString().getBytes(UTF_8), new LongVersion(-1L)));
+ assertTrue(runCommand(new String[] { BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(consoleOutput, consoleOutput.contains(cookie.toString()));
+ verify(rm, times(1)).readCookie(eq(BOOKIE_ID));
+ }
+
+ /**
+ * Run a command to get cookie on an non-existent cookie.
+ */
+ @Test
+ public void testGetNonExistedCookie() throws Exception {
+ doThrow(new CookieNotFoundException())
+ .when(rm).readCookie(eq(BOOKIE_ID));
+
+ assertFalse(runCommand(new String[] { BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Cookie not found for bookie '" + BOOKIE_ID + "'"));
+ verify(rm, times(1)).readCookie(eq(BOOKIE_ID));
+ }
+
+ /**
+ * Run a command to get cookie when exception is thrown.
+ */
+ @Test
+ public void testGetCookieException() throws Exception {
+ doThrow(new OperationRejectedException())
+ .when(rm).readCookie(eq(BOOKIE_ID));
+
+ assertFalse(runCommand(new String[] { BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Exception on getting cookie for bookie '" + BOOKIE_ID + "'"));
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains(OperationRejectedException.class.getName()));
+ verify(rm, times(1)).readCookie(eq(BOOKIE_ID));
+ }
+
+}
diff --git a/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/UpdateCookieCommandTest.java b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/UpdateCookieCommandTest.java
new file mode 100644
index 0000000..308cd2a
--- /dev/null
+++ b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/commands/cookie/UpdateCookieCommandTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.bookkeeper.tools.cli.commands.cookie;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import org.apache.bookkeeper.bookie.BookieException.CookieNotFoundException;
+import org.apache.bookkeeper.bookie.BookieException.OperationRejectedException;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.apache.bookkeeper.tools.cli.helpers.CookieCommandTestBase;
+import org.apache.bookkeeper.tools.common.BKFlags;
+import org.apache.bookkeeper.versioning.Versioned;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * Unit test {@link UpdateCookieCommand}.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ MetadataDrivers.class })
+public class UpdateCookieCommandTest extends CookieCommandTestBase {
+
+ @Rule
+ public final TemporaryFolder testFolder = new TemporaryFolder();
+
+ private final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ private final PrintStream console = new PrintStream(output);
+
+ private boolean runCommand(String[] args) {
+ UpdateCookieCommand updateCmd = new UpdateCookieCommand(console);
+ BKFlags bkFlags = new BKFlags();
+ bkFlags.serviceUri = "zk://127.0.0.1";
+ return updateCmd.apply(bkFlags, args);
+ }
+
+ private String getConsoleOutput() {
+ return new String(output.toByteArray(), UTF_8);
+ }
+
+ /**
+ * Run a command without providing bookie id.
+ */
+ @Test
+ public void testMissingBookieId() {
+ assertFalse(runCommand(new String[] {}));
+ String consoleOutput = getConsoleOutput();
+ assertBookieIdMissing(consoleOutput);
+ }
+
+ private void assertPrintUsage(String consoleOutput) {
+ assertPrintUsage(consoleOutput, "update [flags] <bookie-id>");
+ }
+
+ /**
+ * Run a command without cookie file.
+ */
+ @Test
+ public void testMissingCookieFileOption() {
+ assertFalse(runCommand(new String[] { BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertOptionMissing(consoleOutput, "-cf, --cookie-file");
+ assertPrintUsage(consoleOutput);
+ }
+
+ /**
+ * Run a command with invalid bookie id.
+ */
+ @Test
+ public void testInvalidBookieId() {
+ assertFalse(runCommand(new String[] { "-cf", "test-cookie-file", INVALID_BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertInvalidBookieId(consoleOutput, INVALID_BOOKIE_ID);
+ }
+
+ /**
+ * Run a command with a non-existent cookie file.
+ */
+ @Test
+ public void testUpdateCookieFromNonExistentCookieFile() {
+ String file = "/path/to/non-existent-cookie-file";
+ assertFalse(runCommand(new String[] { "-cf", file, BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertCookieFileNotExists(consoleOutput, file);
+ }
+
+ /**
+ * A successful run.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testUpdateCookieFromExistentCookieFile() throws Exception {
+ File file = testFolder.newFile("test-cookie-file");
+ byte[] content = "test-update-cookie".getBytes(UTF_8);
+ Files.write(Paths.get(file.toURI()), content);
+ String fileName = file.getPath();
+ assertTrue(runCommand(new String[] { "-cf", fileName, BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(consoleOutput, consoleOutput.isEmpty());
+ verify(rm, times(1)).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+ }
+
+ /**
+ * Run a command to update cookie on an non-existent cookie.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testUpdateNonExistedCookie() throws Exception {
+ doThrow(new CookieNotFoundException())
+ .when(rm).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+
+ File file = testFolder.newFile("test-cookie-file");
+ byte[] content = "test-update-cookie".getBytes(UTF_8);
+ Files.write(Paths.get(file.toURI()), content);
+ String fileName = file.getPath();
+ assertFalse(runCommand(new String[] { "-cf", fileName, BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Cookie not found for bookie '" + BOOKIE_ID + "'"));
+ verify(rm, times(1)).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+ }
+
+ /**
+ * Run a command to update cookie when exception is thrown.
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testUpdateCookieException() throws Exception {
+ doThrow(new OperationRejectedException())
+ .when(rm).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+
+ File file = testFolder.newFile("test-cookie-file");
+ byte[] content = "test-update-cookie".getBytes(UTF_8);
+ Files.write(Paths.get(file.toURI()), content);
+ String fileName = file.getPath();
+ assertFalse(runCommand(new String[] { "-cf", fileName, BOOKIE_ID }));
+ String consoleOutput = getConsoleOutput();
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Exception on updating cookie for bookie '" + BOOKIE_ID + "'"));
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains(OperationRejectedException.class.getName()));
+ verify(rm, times(1)).writeCookie(eq(BOOKIE_ID), any(Versioned.class));
+ }
+
+}
diff --git a/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/helpers/BookieShellCommandTest.java b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/helpers/BookieShellCommandTest.java
new file mode 100644
index 0000000..9597cde
--- /dev/null
+++ b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/helpers/BookieShellCommandTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.bookkeeper.tools.cli.helpers;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.apache.bookkeeper.tools.common.BKCommand;
+import org.apache.bookkeeper.tools.framework.CliFlags;
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.junit.Test;
+
+/**
+ * Unit test {@link BookieShellCommand}.
+ */
+public class BookieShellCommandTest {
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testShellCommand() throws Exception {
+ BKCommand<CliFlags> command = mock(BKCommand.class);
+ String shellCommandName = "test-shell-command";
+ CompositeConfiguration conf = new CompositeConfiguration();
+ BookieShellCommand<CliFlags> shellCommand = new BookieShellCommand<>(
+ shellCommandName,
+ command,
+ conf);
+
+ // test `description`
+ assertEquals(
+ shellCommandName + " [options]",
+ shellCommand.description());
+
+ // test `printUsage`
+ shellCommand.printUsage();
+ verify(command, times(1)).usage();
+
+ // test `runCmd`
+ String[] args = new String[] { "arg-1", "arg-2" };
+ shellCommand.runCmd(args);
+ verify(command, times(1))
+ .apply(same(shellCommandName), same(conf), same(args));
+ }
+
+}
diff --git a/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/helpers/CookieCommandTestBase.java b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/helpers/CookieCommandTestBase.java
new file mode 100644
index 0000000..f6f66b6
--- /dev/null
+++ b/tools/all/src/test/java/org/apache/bookkeeper/tools/cli/helpers/CookieCommandTestBase.java
@@ -0,0 +1,91 @@
+/*
+ * 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.bookkeeper.tools.cli.helpers;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+
+import java.util.function.Function;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.apache.bookkeeper.discover.RegistrationManager;
+import org.apache.bookkeeper.meta.MetadataDrivers;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+/**
+ * A test base for testing cookie commands.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ MetadataDrivers.class })
+public class CookieCommandTestBase extends CommandTestBase {
+
+ protected static final String INVALID_BOOKIE_ID = "127.0.0.1";
+ protected static final String BOOKIE_ID = "127.0.0.1:3181";
+
+ protected RegistrationManager rm;
+
+ @Before
+ public void setup() throws Exception {
+ PowerMockito.mockStatic(MetadataDrivers.class);
+ this.rm = mock(RegistrationManager.class);
+ PowerMockito.doAnswer(invocationOnMock -> {
+ Function<RegistrationManager, ?> func = invocationOnMock.getArgument(1);
+ func.apply(rm);
+ return true;
+ }).when(MetadataDrivers.class, "runFunctionWithRegistrationManager",
+ any(ServerConfiguration.class), any(Function.class));
+ }
+
+ protected void assertBookieIdMissing(String consoleOutput) {
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("No bookie id or more bookie ids is specified")
+ );
+ }
+
+ protected void assertInvalidBookieId(String consoleOutput, String bookieId) {
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Invalid bookie id '" + bookieId + "'"));
+ }
+
+ protected void assertOptionMissing(String consoleOutput, String option) {
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("The following option is required: " + option));
+ }
+
+ protected void assertPrintUsage(String consoleOutput, String usage) {
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Usage: " + usage));
+ }
+
+ protected void assertCookieFileNotExists(String consoleOutput, String cookieFile) {
+ assertTrue(
+ consoleOutput,
+ consoleOutput.contains("Cookie file '" + cookieFile + "' doesn't exist."));
+ }
+
+}
diff --git a/tools/framework/src/main/java/org/apache/bookkeeper/tools/common/BKCommand.java b/tools/framework/src/main/java/org/apache/bookkeeper/tools/common/BKCommand.java
index bf591ab..35b2cbd 100644
--- a/tools/framework/src/main/java/org/apache/bookkeeper/tools/common/BKCommand.java
+++ b/tools/framework/src/main/java/org/apache/bookkeeper/tools/common/BKCommand.java
@@ -23,6 +23,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Paths;
import lombok.extern.slf4j.Slf4j;
+import org.apache.bookkeeper.common.annotation.InterfaceAudience.Private;
import org.apache.bookkeeper.common.net.ServiceURI;
import org.apache.bookkeeper.tools.framework.Cli;
import org.apache.bookkeeper.tools.framework.CliCommand;
@@ -50,6 +51,19 @@ public abstract class BKCommand<CommandFlagsT extends CliFlags> extends CliComma
return 0 == Cli.runCli(newSpec, args);
}
+ /**
+ * Made this as public for allowing old bookie shell use new cli command.
+ * This should be removed once we get rid of the old bookie shell.
+ */
+ @Private
+ public int apply(String commandName, CompositeConfiguration conf, String[] args) {
+ CliSpec<CommandFlagsT> newSpec = CliSpec.newBuilder(spec)
+ .withName(commandName)
+ .withRunFunc(cmdFlags -> apply(null, conf, new BKFlags(), cmdFlags))
+ .build();
+ return Cli.runCli(newSpec, args);
+ }
+
protected boolean apply(BKFlags bkFlags, CommandFlagsT cmdFlags) {
ServiceURI serviceURI = null;