This is an automated email from the ASF dual-hosted git repository.

dragonyliu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ratis.git


The following commit(s) were added to refs/heads/master by this push:
     new 9095c895b RATIS-1860. Add ratis-shell cmd to generate a new 
raft-meta.conf. (#901)
9095c895b is described below

commit 9095c895bf72113f99cc3c0b3c084987688e2c5a
Author: chunxiaozheng <[email protected]>
AuthorDate: Mon Aug 21 12:36:17 2023 +0800

    RATIS-1860. Add ratis-shell cmd to generate a new raft-meta.conf. (#901)
    
    * Add ratis-shell cmd to generate a new raft-meta.conf.
    
    * Remove index option.
    
    * Style fix.
    
    * Use GroupCommand as the parent of RaftMetaConfCommand.
    
    * Modified by review comments.
    
    * Update cli.md
    
    * Use LocalCommand as the parent of RaftMetaConfCommand.
    
    * ReConstruct some abstract command.
    
    * Modified by review comments.
    
    * Use println instead of printf.
    
    * Checkstyle error fix(delete some unuse import).
    
    ---------
    
    Co-authored-by: idellzheng <[email protected]>
---
 ratis-docs/src/site/markdown/cli.md                |  12 ++
 .../shell/cli/sh/command/AbstractCommand.java      |  59 +++++++++
 .../cli/sh/command/AbstractParentCommand.java      |   5 +-
 .../shell/cli/sh/command/AbstractRatisCommand.java |  33 +-----
 .../shell/cli/sh/command/ElectionCommand.java      |   3 +-
 .../ratis/shell/cli/sh/command/GroupCommand.java   |   3 +-
 .../{SnapshotCommand.java => LocalCommand.java}    |  19 +--
 .../ratis/shell/cli/sh/command/PeerCommand.java    |   3 +-
 .../shell/cli/sh/command/SnapshotCommand.java      |   3 +-
 .../shell/cli/sh/local/RaftMetaConfCommand.java    | 132 +++++++++++++++++++++
 10 files changed, 227 insertions(+), 45 deletions(-)

diff --git a/ratis-docs/src/site/markdown/cli.md 
b/ratis-docs/src/site/markdown/cli.md
index 75be20c12..60958fc7e 100644
--- a/ratis-docs/src/site/markdown/cli.md
+++ b/ratis-docs/src/site/markdown/cli.md
@@ -80,6 +80,7 @@ Usage: ratis sh [generic options]
          [group [info] [list]]
          [peer [add] [remove] [setPriority]]
          [snapshot [create]]
+         [local [raftMetaConf]]
 ```
 
 ## generic options
@@ -172,3 +173,14 @@ Trigger the specified server take snapshot.
 ```
 $ ratis sh snapshot create -peers 
<P0_HOST:P0_PORT,P1_HOST:P1_PORT,P2_HOST:P2_PORT> -peerId <peerId0> [-groupid 
<RAFT_GROUP_ID>]
 ```
+
+## local
+The `local` command is used to process local operation, which no need to 
connect to ratis server.
+It has the following subcommands:
+`raftMetaConf`
+
+### local raftMetaConf
+Generate a new raft-meta.conf file based on original raft-meta.conf and new 
peers, which is used to move a raft node to a new node.
+```
+$ ratis sh local raftMetaConf -peers 
<P0_HOST:P0_PORT,P1_HOST:P1_PORT,P2_HOST:P2_PORT> -path 
<PARENT_PATH_OF_RAFT_META_CONF>
+```
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractCommand.java
new file mode 100644
index 000000000..20a52a80f
--- /dev/null
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractCommand.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ratis.shell.cli.sh.command;
+
+import org.apache.ratis.shell.cli.Command;
+
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+
+/**
+ * The base class for all the ratis shell {@link Command} classes.
+ */
+public abstract class AbstractCommand implements Command {
+
+  private final PrintStream printStream;
+
+  protected AbstractCommand(Context context) {
+    printStream = context.getPrintStream();
+  }
+
+  public static InetSocketAddress parseInetSocketAddress(String address) {
+    try {
+      final String[] hostPortPair = address.split(":");
+      if (hostPortPair.length < 2) {
+        throw new IllegalArgumentException("Unexpected address format 
<HOST:PORT>.");
+      }
+      return new InetSocketAddress(hostPortPair[0], 
Integer.parseInt(hostPortPair[1]));
+    } catch (Exception e) {
+      throw new IllegalArgumentException("Failed to parse the server address 
parameter \"" + address + "\".", e);
+    }
+  }
+
+  protected PrintStream getPrintStream() {
+    return printStream;
+  }
+
+  protected void printf(String format, Object... args) {
+    printStream.printf(format, args);
+  }
+
+  protected void println(Object message) {
+    printStream.println(message);
+  }
+}
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractParentCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractParentCommand.java
index 01428dfed..ec8401f4f 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractParentCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractParentCommand.java
@@ -26,11 +26,10 @@ import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-public abstract class AbstractParentCommand extends AbstractRatisCommand{
+public abstract class AbstractParentCommand implements Command {
   private final Map<String, Command> subs;
 
-  public AbstractParentCommand(Context context, List<Function<Context, 
AbstractRatisCommand>> subCommandConstructors) {
-    super(context);
+  public AbstractParentCommand(Context context, List<Function<Context, 
Command>> subCommandConstructors) {
     this.subs = Collections.unmodifiableMap(subCommandConstructors.stream()
         .map(constructor -> constructor.apply(context))
         .collect(Collectors.toMap(Command::getCommandName, Function.identity(),
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
index 74fcbae3d..1888c0e0e 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/AbstractRatisCommand.java
@@ -20,7 +20,6 @@ package org.apache.ratis.shell.cli.sh.command;
 import org.apache.commons.cli.Option;
 import org.apache.ratis.protocol.*;
 import org.apache.ratis.protocol.exceptions.RaftException;
-import org.apache.ratis.shell.cli.Command;
 import org.apache.ratis.shell.cli.RaftUtils;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Options;
@@ -34,7 +33,6 @@ import org.apache.ratis.util.ProtoUtils;
 import org.apache.ratis.util.function.CheckedFunction;
 
 import java.io.IOException;
-import java.io.PrintStream;
 import java.net.InetSocketAddress;
 import java.util.*;
 import java.util.function.BiConsumer;
@@ -43,25 +41,13 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
- * The base class for all the ratis shell {@link Command} classes.
+ * The base class for the ratis shell which need to connect to server.
  */
-public abstract class AbstractRatisCommand implements Command {
+public abstract class AbstractRatisCommand extends AbstractCommand {
   public static final String PEER_OPTION_NAME = "peers";
   public static final String GROUPID_OPTION_NAME = "groupid";
   public static final RaftGroupId DEFAULT_RAFT_GROUP_ID = 
RaftGroupId.randomId();
 
-  public static InetSocketAddress parseInetSocketAddress(String address) {
-    try {
-      final String[] hostPortPair = address.split(":");
-      if (hostPortPair.length < 2) {
-        throw new IllegalArgumentException("Unexpected address format 
<HOST:PORT>.");
-      }
-      return new InetSocketAddress(hostPortPair[0], 
Integer.parseInt(hostPortPair[1]));
-    } catch (Exception e) {
-      throw new IllegalArgumentException("Failed to parse the server address 
parameter \"" + address + "\".", e);
-    }
-  }
-
   /**
    * Execute a given function with input parameter from the members of a list.
    *
@@ -86,12 +72,11 @@ public abstract class AbstractRatisCommand implements 
Command {
     return null;
   }
 
-  private final PrintStream printStream;
   private RaftGroup raftGroup;
   private GroupInfoReply groupInfoReply;
 
   protected AbstractRatisCommand(Context context) {
-    printStream = context.getPrintStream();
+    super(context);
   }
 
   @Override
@@ -154,18 +139,6 @@ public abstract class AbstractRatisCommand implements 
Command {
             .addOption(GROUPID_OPTION_NAME, true, "Raft group id");
   }
 
-  protected PrintStream getPrintStream() {
-    return printStream;
-  }
-
-  protected void printf(String format, Object... args) {
-    printStream.printf(format, args);
-  }
-
-  protected void println(Object message) {
-    printStream.println(message);
-  }
-
   protected RaftGroup getRaftGroup() {
     return raftGroup;
   }
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/ElectionCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/ElectionCommand.java
index 43d68467f..054f8c6bb 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/ElectionCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/ElectionCommand.java
@@ -17,6 +17,7 @@
  */
 package org.apache.ratis.shell.cli.sh.command;
 
+import org.apache.ratis.shell.cli.Command;
 import org.apache.ratis.shell.cli.sh.election.PauseCommand;
 import org.apache.ratis.shell.cli.sh.election.ResumeCommand;
 import org.apache.ratis.shell.cli.sh.election.StepDownCommand;
@@ -28,7 +29,7 @@ import java.util.List;
 import java.util.function.Function;
 
 public class ElectionCommand extends AbstractParentCommand {
-  private static final List<Function<Context, AbstractRatisCommand>> 
SUB_COMMAND_CONSTRUCTORS
+  private static final List<Function<Context, Command>> 
SUB_COMMAND_CONSTRUCTORS
       = Collections.unmodifiableList(Arrays.asList(
       TransferCommand::new, StepDownCommand::new, PauseCommand::new, 
ResumeCommand::new));
 
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/GroupCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/GroupCommand.java
index e1d7ec058..69953a982 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/GroupCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/GroupCommand.java
@@ -17,6 +17,7 @@
  */
 package org.apache.ratis.shell.cli.sh.command;
 
+import org.apache.ratis.shell.cli.Command;
 import org.apache.ratis.shell.cli.sh.group.GroupInfoCommand;
 import org.apache.ratis.shell.cli.sh.group.GroupListCommand;
 
@@ -30,7 +31,7 @@ import java.util.function.Function;
  */
 public class GroupCommand extends AbstractParentCommand {
 
-  private static final List<Function<Context, AbstractRatisCommand>> 
SUB_COMMAND_CONSTRUCTORS
+  private static final List<Function<Context, Command>> 
SUB_COMMAND_CONSTRUCTORS
           = Collections.unmodifiableList(Arrays.asList(
           GroupInfoCommand::new, GroupListCommand::new));
   /**
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/SnapshotCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/LocalCommand.java
similarity index 69%
copy from 
ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/SnapshotCommand.java
copy to 
ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/LocalCommand.java
index 4dd584259..4a22b2763 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/SnapshotCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/LocalCommand.java
@@ -17,7 +17,8 @@
  */
 package org.apache.ratis.shell.cli.sh.command;
 
-import org.apache.ratis.shell.cli.sh.snapshot.TakeSnapshotCommand;
+import org.apache.ratis.shell.cli.Command;
+import org.apache.ratis.shell.cli.sh.local.RaftMetaConfCommand;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -25,22 +26,23 @@ import java.util.List;
 import java.util.function.Function;
 
 /**
- * Command for the ratis snapshot
+ * Command for local operation, which no need to connect to ratis server
  */
-public class SnapshotCommand extends AbstractParentCommand {
-  private static final List<Function<Context, AbstractRatisCommand>> 
SUB_COMMAND_CONSTRUCTORS
-      = Collections.unmodifiableList(Arrays.asList(TakeSnapshotCommand::new));
+public class LocalCommand extends AbstractParentCommand {
+
+  private static final List<Function<Context, Command>> 
SUB_COMMAND_CONSTRUCTORS
+      = Collections.unmodifiableList(Arrays.asList(RaftMetaConfCommand::new));
 
   /**
    * @param context command context
    */
-  public SnapshotCommand(Context context) {
+  public LocalCommand(Context context) {
     super(context, SUB_COMMAND_CONSTRUCTORS);
   }
 
   @Override
   public String getCommandName() {
-    return "snapshot";
+    return "local";
   }
 
   @Override
@@ -52,6 +54,7 @@ public class SnapshotCommand extends AbstractParentCommand {
    * @return command's description
    */
   public static String description() {
-    return "Manage ratis snapshot; see the sub-commands for the details.";
+    return "Command for local operation, which no need to connect to ratis 
server; "
+        + "see the sub-commands for the details.";
   }
 }
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/PeerCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/PeerCommand.java
index 6cb279654..2394a568f 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/PeerCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/PeerCommand.java
@@ -17,6 +17,7 @@
  */
 package org.apache.ratis.shell.cli.sh.command;
 
+import org.apache.ratis.shell.cli.Command;
 import org.apache.ratis.shell.cli.sh.peer.AddCommand;
 import org.apache.ratis.shell.cli.sh.peer.RemoveCommand;
 import org.apache.ratis.shell.cli.sh.peer.SetPriorityCommand;
@@ -31,7 +32,7 @@ import java.util.function.Function;
  */
 public class PeerCommand extends AbstractParentCommand{
 
-  private static final List<Function<Context, AbstractRatisCommand>> 
SUB_COMMAND_CONSTRUCTORS
+  private static final List<Function<Context, Command>> 
SUB_COMMAND_CONSTRUCTORS
       = Collections.unmodifiableList(Arrays.asList(AddCommand::new, 
RemoveCommand::new,
       SetPriorityCommand::new));
 
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/SnapshotCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/SnapshotCommand.java
index 4dd584259..34f8786ad 100644
--- 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/SnapshotCommand.java
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/command/SnapshotCommand.java
@@ -17,6 +17,7 @@
  */
 package org.apache.ratis.shell.cli.sh.command;
 
+import org.apache.ratis.shell.cli.Command;
 import org.apache.ratis.shell.cli.sh.snapshot.TakeSnapshotCommand;
 
 import java.util.Arrays;
@@ -28,7 +29,7 @@ import java.util.function.Function;
  * Command for the ratis snapshot
  */
 public class SnapshotCommand extends AbstractParentCommand {
-  private static final List<Function<Context, AbstractRatisCommand>> 
SUB_COMMAND_CONSTRUCTORS
+  private static final List<Function<Context, Command>> 
SUB_COMMAND_CONSTRUCTORS
       = Collections.unmodifiableList(Arrays.asList(TakeSnapshotCommand::new));
 
   /**
diff --git 
a/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/local/RaftMetaConfCommand.java
 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/local/RaftMetaConfCommand.java
new file mode 100644
index 000000000..231c643ac
--- /dev/null
+++ 
b/ratis-shell/src/main/java/org/apache/ratis/shell/cli/sh/local/RaftMetaConfCommand.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ratis.shell.cli.sh.local;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.ratis.proto.RaftProtos.LogEntryProto;
+import org.apache.ratis.proto.RaftProtos.RaftConfigurationProto;
+import org.apache.ratis.proto.RaftProtos.RaftPeerProto;
+import org.apache.ratis.proto.RaftProtos.RaftPeerRole;
+import org.apache.ratis.shell.cli.RaftUtils;
+import org.apache.ratis.shell.cli.sh.command.AbstractCommand;
+import org.apache.ratis.shell.cli.sh.command.Context;
+import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Command for generate a new raft-meta.conf file based on original 
raft-meta.conf and new peers,
+ * which is used to move a raft node to a new node.
+ */
+public class RaftMetaConfCommand extends AbstractCommand {
+  public static final String PEER_OPTION_NAME = "peers";
+  public static final String PATH_OPTION_NAME = "path";
+
+  private static final String RAFT_META_CONF = "raft-meta.conf";
+  private static final String NEW_RAFT_META_CONF = "new-raft-meta.conf";
+
+  /**
+   * @param context command context
+   */
+  public RaftMetaConfCommand(Context context) {
+    super(context);
+  }
+
+  @Override
+  public String getCommandName() {
+    return "raftMetaConf";
+  }
+
+  @Override
+  public int run(CommandLine cl) throws IOException {
+    String peersStr = cl.getOptionValue(PEER_OPTION_NAME);
+    String path = cl.getOptionValue(PATH_OPTION_NAME);
+    if (peersStr == null || path == null || peersStr.isEmpty() || 
path.isEmpty()) {
+      printf("peers or path can't be empty.");
+      return -1;
+    }
+    List<RaftPeerProto> raftPeerProtos = new ArrayList<>();
+    for (String address : peersStr.split(",")) {
+      String peerId = 
RaftUtils.getPeerId(parseInetSocketAddress(address)).toString();
+      raftPeerProtos.add(RaftPeerProto.newBuilder()
+          
.setId(ByteString.copyFrom(peerId.getBytes(StandardCharsets.UTF_8))).setAddress(address)
+          .setStartupRole(RaftPeerRole.FOLLOWER).build());
+    }
+    try (InputStream in = Files.newInputStream(Paths.get(path, 
RAFT_META_CONF));
+         OutputStream out = Files.newOutputStream(Paths.get(path, 
NEW_RAFT_META_CONF))) {
+      long index = LogEntryProto.newBuilder().mergeFrom(in).build().getIndex();
+      println("Index in the original file is: " + index);
+      LogEntryProto generateLogEntryProto = LogEntryProto.newBuilder()
+          .setConfigurationEntry(RaftConfigurationProto.newBuilder()
+              .addAllPeers(raftPeerProtos).build())
+          .setIndex(index + 1).build();
+      printf("Generate new LogEntryProto info is:\n" + generateLogEntryProto);
+      generateLogEntryProto.writeTo(out);
+    }
+    return 0;
+  }
+
+  @Override
+  public String getUsage() {
+    return String.format("%s"
+            + " -%s 
<PEER0_HOST:PEER0_PORT,PEER1_HOST:PEER1_PORT,PEER2_HOST:PEER2_PORT>"
+            + " -%s <PARENT_PATH_OF_RAFT_META_CONF>",
+        getCommandName(), PEER_OPTION_NAME, PATH_OPTION_NAME);
+  }
+
+  @Override
+  public String getDescription() {
+    return description();
+  }
+
+  @Override
+  public Options getOptions() {
+    return new Options()
+        .addOption(
+            Option.builder()
+                .option(PEER_OPTION_NAME)
+                .hasArg()
+                .required()
+                .desc("Peer addresses seperated by comma")
+            .build())
+        .addOption(
+            Option.builder()
+                .option(PATH_OPTION_NAME)
+                .hasArg()
+                .required()
+                .desc("The parent path of raft-meta.conf")
+            .build());
+  }
+
+  /**
+   * @return command's description
+   */
+  public static String description() {
+    return "Generate a new raft-meta.conf file based on original 
raft-meta.conf and new peers.";
+  }
+}
+

Reply via email to