RATIS-2050. Add creationGap param to snapshot management API (#1058)

diff --git a/ratis-client/src/main/java/org/apache/ratis/client/api/SnapshotManagementApi.java b/ratis-client/src/main/java/org/apache/ratis/client/api/SnapshotManagementApi.java
index edd0475..f83d976 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/api/SnapshotManagementApi.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/api/SnapshotManagementApi.java
@@ -27,6 +27,24 @@
  */
 public interface SnapshotManagementApi {
 
-  /** trigger create snapshot file. */
-  RaftClientReply create(long timeoutMs) throws IOException;
+  /** The same as create(0, timeoutMs). */
+  default RaftClientReply create(long timeoutMs) throws IOException {
+    return create(0, timeoutMs);
+  }
+
+    /** The same as create(force? 1 : 0, timeoutMs). */
+  default RaftClientReply create(boolean force, long timeoutMs) throws IOException {
+    return create(force? 1 : 0, timeoutMs);
+  }
+
+    /**
+   * Trigger to create a snapshot.
+   *
+   * @param creationGap When (creationGap > 0) and (astAppliedIndex - lastSnapshotIndex < creationGap),
+   *                    return lastSnapshotIndex; otherwise, take a new snapshot and then return its index.
+   *                    When creationGap == 0, use the server configured value as the creationGap.
+   * @return a reply.  When {@link RaftClientReply#isSuccess()} is true,
+   *         {@link RaftClientReply#getLogIndex()} is the snapshot index fulfilling the operation.
+   */
+  RaftClientReply create(long creationGap, long timeoutMs) throws IOException;
 }
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/ClientProtoUtils.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/ClientProtoUtils.java
index 003f202..cab9606 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/ClientProtoUtils.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/ClientProtoUtils.java
@@ -659,7 +659,8 @@
     switch(p.getOpCase()) {
       case CREATE:
         return SnapshotManagementRequest.newCreate(clientId, serverId,
-            ProtoUtils.toRaftGroupId(m.getRaftGroupId()), m.getCallId(), m.getTimeoutMs());
+            ProtoUtils.toRaftGroupId(m.getRaftGroupId()), m.getCallId(), m.getTimeoutMs(),
+            p.getCreate().getCreationGap());
       default:
         throw new IllegalArgumentException("Unexpected op " + p.getOpCase() + " in " + p);
     }
@@ -671,7 +672,7 @@
         .setRpcRequest(toRaftRpcRequestProtoBuilder(request));
     final SnapshotManagementRequest.Create create = request.getCreate();
     if (create != null) {
-      b.setCreate(SnapshotCreateRequestProto.newBuilder().build());
+      b.setCreate(SnapshotCreateRequestProto.newBuilder().setCreationGap(create.getCreationGap()).build());
     }
     return b.build();
   }
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/SnapshotManagementImpl.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/SnapshotManagementImpl.java
index 1762dc0..65c54d0 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/SnapshotManagementImpl.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/SnapshotManagementImpl.java
@@ -37,9 +37,10 @@
   }
 
   @Override
-  public RaftClientReply create(long timeoutMs) throws IOException {
+  public RaftClientReply create(long creationGap, long timeoutMs) throws IOException {
     final long callId = CallId.getAndIncrement();
     return client.io().sendRequestWithRetry(() -> SnapshotManagementRequest.newCreate(client.getId(),
-        Optional.ofNullable(server).orElseGet(client::getLeaderId), client.getGroupId(), callId, timeoutMs));
+        Optional.ofNullable(server).orElseGet(client::getLeaderId),
+        client.getGroupId(), callId, timeoutMs, creationGap));
   }
 }
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/SnapshotManagementRequest.java b/ratis-common/src/main/java/org/apache/ratis/protocol/SnapshotManagementRequest.java
index 2ea2059..269fdfc 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/SnapshotManagementRequest.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/SnapshotManagementRequest.java
@@ -24,7 +24,16 @@
   public abstract static class Op {
 
   }
-  public static class Create extends Op {
+
+  public static final class Create extends Op {
+    private final long creationGap;
+    private Create(long creationGap) {
+      this.creationGap = creationGap;
+    }
+
+    public long getCreationGap() {
+      return creationGap;
+    }
 
     @Override
     public String toString() {
@@ -35,8 +44,13 @@
 
   public static SnapshotManagementRequest newCreate(ClientId clientId,
       RaftPeerId serverId, RaftGroupId groupId, long callId, long timeoutMs) {
+    return newCreate(clientId, serverId, groupId, callId, timeoutMs, 0);
+  }
+
+  public static SnapshotManagementRequest newCreate(ClientId clientId,
+      RaftPeerId serverId, RaftGroupId groupId, long callId, long timeoutMs, long creationGap) {
     return new SnapshotManagementRequest(clientId,
-        serverId, groupId, callId, timeoutMs,new SnapshotManagementRequest.Create());
+        serverId, groupId, callId, timeoutMs, new SnapshotManagementRequest.Create(creationGap));
   }
 
   private final Op op;
diff --git a/ratis-proto/src/main/proto/Raft.proto b/ratis-proto/src/main/proto/Raft.proto
index edc57ec..b2e96e2 100644
--- a/ratis-proto/src/main/proto/Raft.proto
+++ b/ratis-proto/src/main/proto/Raft.proto
@@ -470,7 +470,7 @@
 }
 
 message SnapshotCreateRequestProto {
-
+  uint64 creationGap = 1;
 }
 
 message StartLeaderElectionRequestProto {
diff --git a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
index 0ea3746..2cec095 100644
--- a/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
+++ b/ratis-server/src/main/java/org/apache/ratis/server/impl/RaftServerImpl.java
@@ -1223,9 +1223,10 @@
     LOG.info("{}: takeSnapshotAsync {}", getMemberId(), request);
     assertLifeCycleState(LifeCycle.States.RUNNING);
     assertGroup(getMemberId(), request);
+    Preconditions.assertNotNull(request.getCreate(), "create");
 
-    //TODO(liuyaolong): get the gap value from shell command
-    long minGapValue = RaftServerConfigKeys.Snapshot.creationGap(proxy.getProperties());
+    final long creationGap = request.getCreate().getCreationGap();
+    long minGapValue = creationGap > 0? creationGap : RaftServerConfigKeys.Snapshot.creationGap(proxy.getProperties());
     final long lastSnapshotIndex = Optional.ofNullable(stateMachine.getLatestSnapshot())
         .map(SnapshotInfo::getIndex)
         .orElse(0L);