* 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,
 * See the License for the specific language governing permissions and
 * limitations under the License.
package com.alipay.sofa.jraft.test.atomic.server;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alipay.sofa.jraft.Node;
import com.alipay.sofa.jraft.RaftGroupService;
import com.alipay.sofa.jraft.Status;
import com.alipay.sofa.jraft.closure.ReadIndexClosure;
import com.alipay.sofa.jraft.conf.Configuration;
import com.alipay.sofa.jraft.entity.PeerId;
import com.alipay.sofa.jraft.option.NodeOptions;
import com.alipay.sofa.jraft.option.ReadOnlyOption;
import com.alipay.sofa.jraft.rpc.RpcContext;
import com.alipay.sofa.jraft.rpc.RpcServer;
import com.alipay.sofa.jraft.test.atomic.KeyNotFoundException;
import com.alipay.sofa.jraft.test.atomic.command.BooleanCommand;
import com.alipay.sofa.jraft.test.atomic.command.ValueCommand;
import com.alipay.sofa.jraft.test.atomic.server.processor.GetCommandProcessor;
import com.alipay.sofa.jraft.util.Bits;
import com.codahale.metrics.ConsoleReporter;

 * Atomic range node in a raft group.
 * @author boyan ([email protected])
 * 2018-Apr-09 4:51:02 PM
public class AtomicRangeGroup {

    final static Logger              LOG       = LoggerFactory.getLogger(AtomicRangeGroup.class);

    private final RaftGroupService   raftGroupService;
    private final Node               node;
    private final AtomicStateMachine fsm;

    private final long               minSlot;                                                    //inclusion
    private final long               maxSlot;                                                    //exclusion
    private final AtomicInteger      requestId = new AtomicInteger(0);

    public long getMinSlot() {
        return this.minSlot;

    public long getMaxSlot() {
        return this.maxSlot;

    public AtomicRangeGroup(String dataPath, String groupId, PeerId serverId, long minSlot, long maxSlot,
                            NodeOptions nodeOptions, RpcServer rpcServer) throws IOException {
        // Init file path
        FileUtils.forceMkdir(new File(dataPath));
        this.minSlot = minSlot;
        this.maxSlot = maxSlot;

        // Init statemachine
        this.fsm = new AtomicStateMachine();

        // Set statemachine to bootstrap options

        // Set the data path
        // Log, required
        nodeOptions.setLogUri(dataPath + File.separator + "log");
        // Metadata, required
        nodeOptions.setRaftMetaUri(dataPath + File.separator + "raft_meta");
        // Snapshot, not required, but recommend
        nodeOptions.setSnapshotUri(dataPath + File.separator + "snapshot");
        // Init raft group service framework
        this.raftGroupService = new RaftGroupService(groupId, serverId, nodeOptions, rpcServer);
        // Startup node
        this.node = this.raftGroupService.start();

        final ConsoleReporter reporter = ConsoleReporter.forRegistry(node.getNodeMetrics().getMetricRegistry())
        reporter.start(60, TimeUnit.SECONDS);


    public void readFromQuorum(final String key, RpcContext asyncContext) {
        final byte[] reqContext = new byte[4];
        Bits.putInt(reqContext, 0, requestId.incrementAndGet());
        this.node.readIndex(reqContext, new ReadIndexClosure() {

            public void run(Status status, long index, byte[] reqCtx) {
                if (status.isOk()) {
                    try {
                        asyncContext.sendResponse(new ValueCommand(fsm.getValue(key)));
                    } catch (final KeyNotFoundException e) {
                } else {
                    asyncContext.sendResponse(new BooleanCommand(false, status.getErrorMsg()));

    public AtomicStateMachine getFsm() {
        return this.fsm;

    public Node getNode() {
        return this.node;

    public RaftGroupService RaftGroupService() {
        return this.raftGroupService;

     * Redirect request to new leader
     * @return
    public BooleanCommand redirect() {
        final BooleanCommand response = new BooleanCommand();
        response.setErrorMsg("Not leader");
        if (node != null) {
            final PeerId leader = node.getLeaderId();
            if (leader != null) {

        return response;

    public static AtomicRangeGroup start(StartupConf conf, RpcServer rpcServer) throws IOException {

        final NodeOptions nodeOptions = new NodeOptions();
        // Set election timeout to 1 second
        // Close cli service
        // A snapshot saving would be triggered every 30 seconds
        // nodeOptions.setSnapshotIntervalSecs(30);
        // Parsing Options
        final PeerId serverId = new PeerId();
        if (!serverId.parse(conf.getServerAddress())) {
            throw new IllegalArgumentException("Fail to parse serverId:" + conf.getServerAddress());
        final Configuration initConf = new Configuration();
        if (!initConf.parse(conf.getConf())) {
            throw new IllegalArgumentException("Fail to parse initConf:" + conf.getConf());
        // Set the initial cluster configuration
        // Startup node
        final AtomicRangeGroup node = new AtomicRangeGroup(conf.getDataPath(), conf.getGroupId(), serverId,
            conf.getMinSlot(), conf.getMaxSlot(), nodeOptions, rpcServer);
        LOG.info("Started range node[{}-{}] at port:{}", conf.getMinSlot(), conf.getMaxSlot(), node.getNode()
        return node;