Package net.kuujo.copycat.internal.state

Source Code of net.kuujo.copycat.internal.state.StateContext

/*
* Copyright 2014 the original author or authors.
*
* Licensed 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 net.kuujo.copycat.internal.state;

import net.kuujo.copycat.CopycatConfig;
import net.kuujo.copycat.CopycatException;
import net.kuujo.copycat.CopycatState;
import net.kuujo.copycat.StateMachine;
import net.kuujo.copycat.cluster.Cluster;
import net.kuujo.copycat.cluster.Member;
import net.kuujo.copycat.event.LeaderElectEvent;
import net.kuujo.copycat.event.StartEvent;
import net.kuujo.copycat.event.StateChangeEvent;
import net.kuujo.copycat.event.StopEvent;
import net.kuujo.copycat.internal.StateMachineExecutor;
import net.kuujo.copycat.internal.cluster.ClusterManager;
import net.kuujo.copycat.internal.event.DefaultEventHandlers;
import net.kuujo.copycat.internal.util.Assert;
import net.kuujo.copycat.log.Log;
import net.kuujo.copycat.protocol.Response;
import net.kuujo.copycat.protocol.SubmitRequest;
import net.kuujo.copycat.spi.protocol.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

/**
* Raft state context.
*
* @author <a href="http://github.com/kuujo">Jordan Halterman</a>
*/
public final class StateContext {
  private static final Logger LOGGER = LoggerFactory.getLogger(StateContext.class);
  @SuppressWarnings("rawtypes")
  private final Cluster cluster;
  @SuppressWarnings("rawtypes")
  private final ClusterManager clusterManager;
  private final StateMachineExecutor stateMachineExecutor;
  private final Log log;
  private final CopycatConfig config;
  private final DefaultEventHandlers events = new DefaultEventHandlers();
  private volatile StateController currentState;
  private volatile String currentLeader;
  private volatile long currentTerm;
  private volatile String lastVotedFor;
  private volatile long commitIndex = 0;
  private volatile long lastApplied = 0;

  public <M extends Member> StateContext(StateMachine stateMachine, Log log, Cluster<M> cluster, Protocol<M> protocol, CopycatConfig config) {
    this.stateMachineExecutor = new StateMachineExecutor(stateMachine);
    this.log = log;
    this.config = config;
    this.cluster = cluster;
    this.clusterManager = new ClusterManager<>(cluster, protocol);
  }

  /**
   * Returns the state machine.
   *
   * @return The state machine.
   */
  public StateMachineExecutor stateMachineExecutor() {
    return stateMachineExecutor;
  }

  /**
   * Returns the copycat configuration.
   *
   * @return The copycat configuration.
   */
  public CopycatConfig config() {
    return config;
  }

  /**
   * Returns the copycat cluster.
   *
   * @return The copycat cluster.
   */
  @SuppressWarnings("rawtypes")
  public Cluster cluster() {
    return cluster;
  }

  /**
   * Returns the copycat cluster manager.
   *
   * @return The copycat cluster manager.
   */
  @SuppressWarnings("rawtypes")
  public ClusterManager clusterManager() {
    return clusterManager;
  }

  /**
   * Returns the copycat log.
   *
   * @return The copycat log.
   */
  public Log log() {
    return log;
  }

  /**
   * Returns the internal event registry.
   *
   * @return The internal event registry.
   */
  public DefaultEventHandlers events() {
    return events;
  }

  /**
   * Returns the current state.
   *
   * @return The current state.
   */
  public CopycatState state() {
    return currentState.state();
  }

  /**
   * Starts the state.
   *
   * @return A completable future to be called once complete.
   */
  public CompletableFuture<Void> start() {
    // Set the local the remote internal cluster members at startup. This may
    // be overwritten by the logs once the replica has been started.
    LOGGER.info("{} Starting context", clusterManager.localNode());
    transition(NoneController.class);
    checkConfiguration();
    return clusterManager.localNode().server().listen().whenComplete((result, error) -> {
      try {
        log.open();
      } catch (Exception e) {
        throw new CopycatException(e);
      }
      transition(FollowerController.class);
      events.start().handle(new StartEvent());
    });
  }

  /**
   * Checks the replica configuration and logs helpful warnings.
   */
  private void checkConfiguration() {
    if (clusterManager.remoteNodes().isEmpty()) {
      LOGGER.warn("{} - No remote nodes in the cluster!", clusterManager.localNode());
    }
    if (!config.isRequireQueryQuorum()) {
      LOGGER.warn("{} - Read quorums are disabled! This can cause stale reads!",
          clusterManager.localNode());
    }
    if (!config.isRequireCommandQuorum()) {
      LOGGER.warn("{} - Write quorums are disabled! This can cause data loss!",
          clusterManager.localNode());
    }
    if (config.getElectionTimeout() < config.getHeartbeatInterval()) {
      LOGGER.error("{} - Election timeout is greater than heartbeat interval!",
          clusterManager.localNode());
    }
  }

  /**
   * Stops the state.
   *
   * @return A completable future to be called once complete.
   */
  public CompletableFuture<Void> stop() {
    LOGGER.info("{} - Stopping context", clusterManager.localNode());
    return clusterManager.localNode().server().close().whenComplete((result, error) -> {
      try {
        log.close();
      } catch (Exception e) {
        throw new CopycatException(e);
      }
      transition(NoneController.class);
      events.stop().handle(new StopEvent());
    });
  }

  /**
   * Transitions to a new state.
   *
   * @throws NullPointerException if {@code type} is null
   */
  public synchronized void transition(Class<? extends StateController> type) {
    Assert.isNotNull(type, "type");
    if ((currentState == null && type == null)
        || (currentState != null && type != null && type.isAssignableFrom(currentState.getClass()))) {
      return;
    }

    final StateController oldState = currentState;
    try {
      currentState = type.newInstance();
      LOGGER.info("{} - Transitioning to {}", clusterManager.localNode(), currentState.state());
    } catch (InstantiationException | IllegalAccessException e) {
      // Log the exception.
    }
    if (oldState != null) {
      oldState.destroy();
      currentState.init(this);
    } else {
      currentState.init(this);
    }

    events.stateChange().handle(new StateChangeEvent(currentState.state()));
  }

  /**
   * Returns the current leader.
   */
  public String currentLeader() {
    return currentLeader;
  }

  /**
   * Sets the current leader.
   */
  public StateContext currentLeader(String leader) {
    if (currentLeader == null || !currentLeader.equals(leader)) {
      LOGGER.debug("{} - currentLeader: {}", clusterManager.localNode(), leader);
    }

    if (currentLeader == null && leader != null) {
      currentLeader = leader;
      events.leaderElect().handle(new LeaderElectEvent(currentTerm, clusterManager.node(currentLeader).member()));
    } else if (currentLeader != null && leader != null && !currentLeader.equals(leader)) {
      currentLeader = leader;
      events.leaderElect().handle(new LeaderElectEvent(currentTerm, clusterManager.node(currentLeader).member()));
    } else {
      currentLeader = leader;
    }
    return this;
  }

  /**
   * Returns a boolean indicating whether this node is leader.
   *
   * @return Indicates whether this node is the leader.
   */
  public boolean isLeader() {
    return currentLeader != null && currentLeader.equals(clusterManager.localNode().member().id());
  }

  /**
   * Returns the current term.
   */
  public long currentTerm() {
    return currentTerm;
  }

  /**
   * Sets the current term.
   */
  public StateContext currentTerm(long term) {
    if (term > currentTerm) {
      currentTerm = term;
      LOGGER.debug("{} - currentTerm: {}", clusterManager.localNode(), term);
      lastVotedFor = null;
    }
    return this;
  }

  /**
   * Returns the candidate last voted for.
   */
  public String lastVotedFor() {
    return lastVotedFor;
  }

  /**
   * Sets the candidate last voted for.
   */
  public StateContext lastVotedFor(String candidate) {
    if (lastVotedFor == null || !lastVotedFor.equals(candidate)) {
      LOGGER.debug("{} - lastVotedFor: {}", clusterManager.localNode(), candidate);
    }
    lastVotedFor = candidate;
    return this;
  }

  /**
   * Returns the commit index.
   */
  public long commitIndex() {
    return commitIndex;
  }

  /**
   * Sets the commit index.
   */
  public StateContext commitIndex(long index) {
    LOGGER.debug("{} - commitIndex: {}", clusterManager.localNode(), index);
    commitIndex = Math.max(commitIndex, index);
    return this;
  }

  /**
   * Returns the last applied index.
   */
  public long lastApplied() {
    return lastApplied;
  }

  /**
   * Sets the last applied index.
   */
  public StateContext lastApplied(long index) {
    LOGGER.debug("{} - lastApplied: {}", clusterManager.localNode(), index);
    lastApplied = index;
    return this;
  }

  /**
   * Returns the next correlation ID from the correlation ID generator.
   */
  public Object nextCorrelationId() {
    return config.getCorrelationStrategy().nextCorrelationId();
  }

  /**
   * Submits an operation to the state.
   *
   * @param operation The operation to submit.
   * @param args The operation arguments.
   * @param <R> The operation return type.
   * @return A completable future to be completed with the command result.
   */
  @SuppressWarnings("unchecked")
  public <R> CompletableFuture<R> submit(final String operation, final Object... args) {
    final CompletableFuture<R> future = new CompletableFuture<>();
    if (currentState == null) {
      future.completeExceptionally(new CopycatException("Invalid copycat state"));
      return future;
    }

    currentState.submit(new SubmitRequest(nextCorrelationId(), operation, Arrays.asList(args))).whenComplete((response, error) -> {
      if (error != null) {
        future.completeExceptionally(error);
      } else {
        if (response.status().equals(Response.Status.OK)) {
          future.complete((R) response.result());
        } else {
          future.completeExceptionally(response.error());
        }
      }
    });
    return future;
  }

  @Override
  public String toString() {
    String value = "StateContext";
    value += "[\n";
    value += String.format("memberId=%s", clusterManager.localNode().member().id());
    value += ",\n";
    value += String.format("state=%s", currentState.state());
    value += ",\n";
    value += String.format("term=%d", currentTerm);
    value += ",\n";
    value += String.format("leader=%s", currentLeader);
    value += ",\n";
    value += String.format("lastVotedFor=%s", lastVotedFor);
    value += ",\n";
    value += String.format("commitIndex=%s", commitIndex);
    value += ",\n";
    value += String.format("lastApplied=%s", lastApplied);
    value += "\n]";
    return value;
  }

}
TOP

Related Classes of net.kuujo.copycat.internal.state.StateContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.