/*
* ToroDB
* Copyright © 2014 8Kdata Technology (www.8kdata.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.torodb.mongodb.commands.pojos;
import com.eightkdata.mongowp.OpTime;
import com.eightkdata.mongowp.bson.BsonTimestamp;
import com.eightkdata.mongowp.bson.utils.DefaultBsonValues;
import com.google.common.net.HostAndPort;
import com.torodb.mongodb.commands.signatures.internal.ReplSetHeartbeatReply;
import com.torodb.mongodb.commands.signatures.internal.ReplSetHeartbeatReplyBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.time.Instant;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This class contains the data returned from a heartbeat command for one member of a replica set.
*/
//TODO(gortiz): this class generates some extra optionals that can be optimized
public class MemberHeartbeatData {
private static final Logger LOGGER = LogManager.getLogger(MemberHeartbeatData.class);
private Health health;
@Nullable
private Instant upSince;
private Instant lastHeartbeat;
@Nullable
private Instant lastHeartbeatRecv;
private boolean authIssue;
@Nonnull
private ReplSetHeartbeatReply lastResponse;
public MemberHeartbeatData() {
this.health = Health.NOT_CHECKED;
this.upSince = Instant.EPOCH;
this.lastHeartbeat = Instant.EPOCH;
this.lastHeartbeatRecv = Instant.EPOCH;
this.authIssue = false;
lastResponse = new ReplSetHeartbeatReplyBuilder()
.setSetName("_unnamed")
.setElectionTime(DefaultBsonValues.newTimestamp(0, 0))
.setState(MemberState.RS_UNKNOWN)
.setAppliedOpTime(OpTime.EPOCH)
.build();
}
public MemberHeartbeatData(
Health health,
@Nullable Instant upSince,
Instant lastHeartbeat,
@Nullable Instant lastHeartbeatRecv,
boolean authIssue,
ReplSetHeartbeatReply lastResponse) {
this.health = health;
this.upSince = upSince;
this.lastHeartbeat = lastHeartbeat;
this.lastHeartbeatRecv = lastHeartbeatRecv;
this.authIssue = authIssue;
this.lastResponse = lastResponse;
}
public Health getHealth() {
return health;
}
@Nullable
public Instant getUpSince() {
return upSince;
}
public Instant getLastHeartbeat() {
return lastHeartbeat;
}
/**
* Returns the instant when we recived the last heartbeat from this node.
*
* @return the instant when we recived the last heartbeat from this node.
*/
@Nullable
public Instant getLastHeartbeatRecv() {
return lastHeartbeatRecv;
}
public boolean isAuthIssue() {
return authIssue;
}
public ReplSetHeartbeatReply getLastResponse() {
return lastResponse;
}
@Nullable
public MemberState getState() {
return lastResponse.getState().orElse(null);
}
@Nullable
public OpTime getOpTime() {
return lastResponse.getAppliedOpTime().orElse(null);
}
@Nonnull
public String getLastHeartbeatMessage() {
return lastResponse.getHbmsg();
}
@Nullable
public HostAndPort getSyncSource() {
return lastResponse.getSyncingTo().orElse(null);
}
@Nullable
public BsonTimestamp getElectionTime() {
return lastResponse.getElectionTime().orElse(null);
}
public long getConfigVersion() {
return lastResponse.getConfigVersion();
}
/**
* @return true iff the member is up or if no heartbeat has been received from him yet.
*/
public boolean maybeUp() {
return health != Health.NOT_CHECKED;
}
public boolean isUp() {
return health == Health.UP;
}
public boolean isUnelectable() {
return lastResponse.getElectable().get();
}
public void setUpValues(@Nonnull Instant now, @Nonnull HostAndPort host,
@Nonnull ReplSetHeartbeatReply hbResponse) {
health = Health.UP;
if (upSince.equals(Instant.EPOCH)) {
upSince = now;
}
authIssue = false;
lastHeartbeat = now;
ReplSetHeartbeatReplyBuilder lastResponseBuilder = new ReplSetHeartbeatReplyBuilder(
hbResponse, lastResponse);
// Log if the state changes
if (!lastResponse.getState().get().equals(hbResponse.getState().get())) {
LOGGER.info("Member {} is now in state {}", host, hbResponse.getState().get());
}
lastResponse = lastResponseBuilder.build();
}
public void setAuthIssue(Instant now) {
health = Health.UNREACHABLE; // set health to 0 so that this doesn't count towards majority.
upSince = Instant.EPOCH;
lastHeartbeat = now;
authIssue = true;
lastResponse = new ReplSetHeartbeatReplyBuilder()
.setSetName(lastResponse.getSetName())
.setElectionTime(DefaultBsonValues.newTimestamp(0, 0))
.setState(MemberState.RS_UNKNOWN)
.setAppliedOpTime(OpTime.EPOCH)
.setSyncingTo(null)
.build();
}
public void setDownValues(Instant now, @Nonnull String errorDesc) {
health = Health.UNREACHABLE; // set health to 0 so that this doesn't count towards majority.
upSince = Instant.EPOCH;
lastHeartbeat = now;
authIssue = false;
lastResponse = new ReplSetHeartbeatReplyBuilder()
.setSetName(lastResponse.getSetName())
.setElectionTime(DefaultBsonValues.newTimestamp(0, 0))
.setHbmsg(errorDesc)
.setState(MemberState.RS_DOWN)
.setAppliedOpTime(OpTime.EPOCH)
.setSyncingTo(null)
.build();
}
public static enum Health {
NOT_CHECKED(-1),
UNREACHABLE(0),
UP(1);
private final double id;
private Health(double id) {
this.id = id;
}
public double getId() {
return id;
}
public static Health fromId(double id) throws IllegalArgumentException {
for (Health value : Health.values()) {
if (Double.compare(value.getId(), id) == 0) {
return value;
}
}
throw new IllegalArgumentException("There is no valid health element whose id is '"
+ id + "'");
}
}
public static class Builder {
private Health health;
private Instant upSince;
private Instant lastHeartbeat;
private Instant lastHeartbeatRecv;
private boolean authIssue;
private ReplSetHeartbeatReply lastResponse;
public Builder() {
}
public Builder(MemberHeartbeatData other) {
this.health = other.health;
this.upSince = other.upSince;
this.lastHeartbeat = other.lastHeartbeat;
this.lastHeartbeatRecv = other.lastHeartbeatRecv;
this.authIssue = other.authIssue;
this.lastResponse = other.lastResponse;
}
public Health getHealth() {
return health;
}
public Builder setHealth(Health health) {
this.health = health;
return this;
}
public Instant getUpSince() {
return upSince;
}
public Builder setUpSince(Instant upSince) {
this.upSince = upSince;
return this;
}
public Instant getLastHeartbeat() {
return lastHeartbeat;
}
public Builder setLastHeartbeat(Instant lastHeartbeat) {
this.lastHeartbeat = lastHeartbeat;
return this;
}
public Instant getLastHeartbeatRecv() {
return lastHeartbeatRecv;
}
public Builder setLastHeartbeatRecv(Instant lastHeartbeatRecv) {
this.lastHeartbeatRecv = lastHeartbeatRecv;
return this;
}
public boolean isAuthIssue() {
return authIssue;
}
public Builder setAuthIssue(boolean authIssue) {
this.authIssue = authIssue;
return this;
}
public ReplSetHeartbeatReply getLastResponse() {
return lastResponse;
}
public Builder setLastResponse(ReplSetHeartbeatReply lastResponse) {
this.lastResponse = lastResponse;
return this;
}
public MemberHeartbeatData build() {
return new MemberHeartbeatData(health, upSince, lastHeartbeat, lastHeartbeatRecv,
authIssue, lastResponse);
}
}
}