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 <guosi...@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 <eolive...@gmail.com>, Jia Zhai <None>, Matteo Merli <mme...@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;