/*
* 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.signatures.repl;
import com.eightkdata.mongowp.bson.BsonArray;
import com.eightkdata.mongowp.bson.BsonDocument;
import com.eightkdata.mongowp.bson.BsonDocument.Entry;
import com.eightkdata.mongowp.bson.BsonObjectId;
import com.eightkdata.mongowp.bson.BsonValue;
import com.eightkdata.mongowp.bson.utils.DefaultBsonValues;
import com.eightkdata.mongowp.exceptions.FailedToParseException;
import com.eightkdata.mongowp.exceptions.NoSuchKeyException;
import com.eightkdata.mongowp.exceptions.TypesMismatchException;
import com.eightkdata.mongowp.fields.ArrayField;
import com.eightkdata.mongowp.fields.BooleanField;
import com.eightkdata.mongowp.fields.DateTimeField;
import com.eightkdata.mongowp.fields.DocField;
import com.eightkdata.mongowp.fields.HostAndPortField;
import com.eightkdata.mongowp.fields.IntField;
import com.eightkdata.mongowp.fields.ObjectIdField;
import com.eightkdata.mongowp.fields.StringField;
import com.eightkdata.mongowp.server.api.impl.AbstractNotAliasableCommand;
import com.eightkdata.mongowp.server.api.tools.Empty;
import com.eightkdata.mongowp.utils.BsonArrayBuilder;
import com.eightkdata.mongowp.utils.BsonDocumentBuilder;
import com.eightkdata.mongowp.utils.BsonReaderTool;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HostAndPort;
import com.google.common.primitives.UnsignedInteger;
import com.torodb.mongodb.commands.pojos.MemberState;
import com.torodb.mongodb.commands.signatures.repl.IsMasterCommand.IsMasterReply;
import com.torodb.mongodb.commands.tools.EmptyCommandArgumentMarshaller;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
public class IsMasterCommand extends AbstractNotAliasableCommand<Empty, IsMasterReply> {
public static final IsMasterCommand INSTANCE = new IsMasterCommand();
private IsMasterCommand() {
super("isMaster");
}
@Override
public Class<? extends Empty> getArgClass() {
return Empty.class;
}
@Override
public Empty unmarshallArg(BsonDocument requestDoc) {
return Empty.getInstance();
}
@Override
public BsonDocument marshallArg(Empty request) {
return EmptyCommandArgumentMarshaller.marshallEmptyArgument(this);
}
@Override
public Class<? extends IsMasterReply> getResultClass() {
return IsMasterReply.class;
}
@Override
public BsonDocument marshallResult(IsMasterReply reply) {
return reply.marshall();
}
@Override
public IsMasterReply unmarshallResult(BsonDocument replyDoc) throws TypesMismatchException,
NoSuchKeyException, FailedToParseException {
return IsMasterReply.unmarshall(replyDoc);
}
@Immutable
public static class IsMasterReply {
public static final IsMasterReply NOT_CONFIGURED = new IsMasterReply();
private static final BooleanField IS_MASTER_FIELD = new BooleanField("ismaster");
private static final BooleanField SECONDARY_FIELD = new BooleanField("secondary");
private static final StringField SET_NAME_FIELD = new StringField("setName");
private static final IntField SET_VERSION_FIELD = new IntField("setVersion");
private static final ArrayField HOSTS_FIELD = new ArrayField("hosts");
private static final ArrayField PASSIVES_FIELD = new ArrayField("passives");
private static final ArrayField ARBITERS_FIELD = new ArrayField("arbiters");
private static final HostAndPortField PRIMARY_FIELD = new HostAndPortField("primary");
private static final BooleanField ARBITER_ONLY_FIELD = new BooleanField("arbiterOnly");
private static final BooleanField PASSIVE_FIELD = new BooleanField("passive");
private static final BooleanField HIDDEN_FIELD = new BooleanField("hidden");
private static final BooleanField BUILD_INDEXES_FIELD = new BooleanField("buildIndexes");
private static final IntField SLAVE_DELAY_FIELD = new IntField("slaveDelay");
private static final DocField TAGS_FIELD = new DocField("tags");
private static final HostAndPortField ME_FIELD = new HostAndPortField("me");
private static final ObjectIdField ELECTION_ID_FIELD = new ObjectIdField("electionId");
private static final StringField INFO_FIELD = new StringField("info");
private static final BooleanField IS_REPLICA_SET_FIELD = new BooleanField("isreplicaset");
private static final IntField MAX_BSON_OBJECT_SIZE = new IntField("maxBsonObjectSize");
private static final IntField MAX_MESSAGE_SIZE_BYTES = new IntField("maxMessageSizeBytes");
private static final IntField MAX_WRITE_BATCH_SIZE = new IntField("maxWriteBatchSize");
private static final DateTimeField LOCAL_TIME = new DateTimeField("localTime");
private static final IntField MAX_WIRE_VERSION = new IntField("maxWireVersion");
private static final IntField MIN_WIRE_VERSION = new IntField("minWireVersion");
private final boolean master;
private final boolean secondary;
private final String setName;
private final Integer setVersion;
private final List<HostAndPort> hosts;
private final List<HostAndPort> passives;
private final List<HostAndPort> arbiters;
private final HostAndPort primary;
private final Boolean arbiterOnly;
private final Boolean passive;
private final Boolean hidden;
private final Boolean buildIndexes;
private final UnsignedInteger slaveDelay;
private final Map<String, String> tags;
private final HostAndPort me;
private final BsonObjectId electionId;
private final boolean configSet;
private final boolean standalone;
private final int maxBsonObjectSize;
private final int maxMessageSizeBytes;
private final int maxWriteBatchSize;
private final Instant localTime;
private final int maxWireVersion;
private final int minWireVersion;
private IsMasterReply(
boolean master,
boolean secondary,
@Nonnull String setName,
Integer setVersion,
@Nullable List<HostAndPort> hosts,
@Nullable List<HostAndPort> passives,
@Nullable List<HostAndPort> arbiters,
HostAndPort primary,
Boolean arbiterOnly,
Boolean passive,
Boolean hidden,
Boolean buildIndexes,
UnsignedInteger slaveDelay,
@Nullable Map<String, String> tags,
@Nonnull HostAndPort me,
@Nullable BsonObjectId electionId,
int maxBsonObjectSize,
int maxMessageSizeBytes,
int maxWriteBatchSize,
Instant localTime,
int maxWireVersion,
int minWireVersion) {
this.master = master;
this.secondary = secondary;
assert !(master & secondary);
this.setName = setName;
this.setVersion = setVersion;
this.hosts = hosts;
this.passives = passives;
this.arbiters = arbiters;
this.primary = primary;
this.arbiterOnly = arbiterOnly;
this.passive = passive;
this.hidden = hidden;
this.buildIndexes = buildIndexes;
this.slaveDelay = slaveDelay;
this.tags = tags;
this.me = me;
this.electionId = electionId;
this.maxBsonObjectSize = maxBsonObjectSize;
this.maxMessageSizeBytes = maxMessageSizeBytes;
this.maxWriteBatchSize = maxWriteBatchSize;
this.localTime = localTime;
this.maxWireVersion = maxWireVersion;
this.minWireVersion = minWireVersion;
configSet = true;
standalone = false;
}
private IsMasterReply() {
this.configSet = false;
this.standalone = false;
this.master = false;
this.secondary = false;
this.setName = null;
this.setVersion = null;
this.hosts = null;
this.passives = null;
this.arbiters = null;
this.primary = null;
this.arbiterOnly = null;
this.passive = null;
this.hidden = null;
this.buildIndexes = null;
this.slaveDelay = null;
this.tags = null;
this.me = null;
this.electionId = null;
this.maxBsonObjectSize = 0;
this.maxMessageSizeBytes = 0;
this.maxWriteBatchSize = 0;
this.localTime = null;
this.maxWireVersion = 0;
this.minWireVersion = 0;
}
public boolean isMaster() {
return master;
}
public boolean isSecondary() {
return secondary;
}
public String getSetName() {
return setName;
}
public Integer getSetVersion() {
return setVersion;
}
public List<HostAndPort> getHosts() {
return hosts;
}
public List<HostAndPort> getPassives() {
return passives;
}
public List<HostAndPort> getArbiters() {
return arbiters;
}
public Boolean isArbiterOnly() {
return arbiterOnly;
}
public Boolean isPassive() {
return passive;
}
public Boolean isHidden() {
return hidden;
}
public Boolean shouldBuildIndexes() {
return buildIndexes;
}
public UnsignedInteger getSlaveDelay() {
return slaveDelay;
}
public ImmutableMap<String, String> getTags() {
return ImmutableMap.copyOf(tags);
}
public HostAndPort getMe() {
return me;
}
public BsonObjectId getElectionId() {
return electionId;
}
public boolean isConfigSet() {
return configSet;
}
private BsonDocument marshall() {
BsonDocumentBuilder builder = new BsonDocumentBuilder();
if (standalone) {
builder.append(IS_MASTER_FIELD, true);
builder.append(MAX_BSON_OBJECT_SIZE, maxBsonObjectSize);
builder.append(MAX_MESSAGE_SIZE_BYTES, maxMessageSizeBytes);
builder.append(MAX_WRITE_BATCH_SIZE, maxWriteBatchSize);
builder.append(LOCAL_TIME, localTime);
builder.append(MAX_WIRE_VERSION, maxWireVersion);
builder.append(MIN_WIRE_VERSION, minWireVersion);
return builder.build();
}
if (!configSet) {
builder.append(IS_MASTER_FIELD, false);
builder.append(SECONDARY_FIELD, false);
builder.append(INFO_FIELD, "Does not have a valid replica set config");
builder.append(IS_REPLICA_SET_FIELD, true);
return builder.build();
}
assert setName != null;
builder.append(SET_NAME_FIELD, setName);
assert setVersion != null;
builder.append(SET_VERSION_FIELD, setVersion);
builder.append(IS_MASTER_FIELD, master);
builder.append(SECONDARY_FIELD, secondary);
if (hosts != null) {
builder.append(HOSTS_FIELD, toBsonArray(hosts));
}
if (passives != null) {
builder.append(PASSIVES_FIELD, toBsonArray(passives));
}
if (arbiters != null) {
builder.append(ARBITERS_FIELD, toBsonArray(passives));
}
if (primary != null) {
builder.append(PRIMARY_FIELD, primary);
}
if (arbiterOnly != null) {
builder.append(ARBITER_ONLY_FIELD, arbiterOnly);
}
if (passive != null) {
builder.append(PASSIVE_FIELD, passive);
}
if (hidden != null) {
builder.append(HIDDEN_FIELD, hidden);
}
if (buildIndexes != null) {
builder.append(BUILD_INDEXES_FIELD, buildIndexes);
}
if (slaveDelay != null) {
builder.append(SLAVE_DELAY_FIELD, slaveDelay.intValue());
}
if (tags != null) {
builder.append(TAGS_FIELD, toBsonDocument(tags));
}
assert me != null;
builder.append(ME_FIELD, me);
if (electionId != null) {
builder.append(ELECTION_ID_FIELD, electionId);
}
builder.append(MAX_BSON_OBJECT_SIZE, maxBsonObjectSize);
builder.append(MAX_MESSAGE_SIZE_BYTES, maxMessageSizeBytes);
builder.append(MAX_WRITE_BATCH_SIZE, maxWriteBatchSize);
builder.append(LOCAL_TIME, localTime);
builder.append(MAX_WIRE_VERSION, maxWireVersion);
builder.append(MIN_WIRE_VERSION, minWireVersion);
return builder.build();
}
private BsonArray toBsonArray(@Nonnull List<HostAndPort> hostsAndPortList) {
BsonArrayBuilder bsonArray = new BsonArrayBuilder();
for (HostAndPort hostAndPort : hostsAndPortList) {
bsonArray.add(hostAndPort.toString());
}
return bsonArray.build();
}
private BsonDocument toBsonDocument(Map<String, String> map) {
BsonDocumentBuilder doc = new BsonDocumentBuilder();
for (java.util.Map.Entry<String, String> entrySet : map.entrySet()) {
doc.appendUnsafe(entrySet.getKey(), DefaultBsonValues.newString(entrySet.getValue()));
}
return doc.build();
}
private static ImmutableList<HostAndPort> fromBsonArray(BsonDocument bson, ArrayField field)
throws TypesMismatchException, NoSuchKeyException {
if (!bson.containsKey(field.getFieldName())) {
return ImmutableList.of();
} else {
ImmutableList.Builder<HostAndPort> resultBuilder = ImmutableList.builder();
BsonArray uncastedList = BsonReaderTool.getArray(bson, field);
for (int i = 0; i < uncastedList.size(); i++) {
BsonValue uncastedValue = uncastedList.get(i);
if (!uncastedValue.isString()) {
throw new TypesMismatchException(
Integer.toString(i),
"string",
uncastedValue.getType(),
"Elements in \"" + field + "\" array of isMaster "
+ "response must be of type string but "
+ "found type " + uncastedValue.getType()
);
}
resultBuilder.add(BsonReaderTool.getHostAndPort(uncastedList.asString().getValue()));
}
return resultBuilder.build();
}
}
private static IsMasterReply unmarshall(BsonDocument bson) throws TypesMismatchException,
NoSuchKeyException, FailedToParseException {
boolean master = BsonReaderTool.getBoolean(bson, IS_MASTER_FIELD);
boolean secondary = BsonReaderTool.getBoolean(bson, SECONDARY_FIELD);
if (bson.containsKey(INFO_FIELD.getFieldName())) {
if (master || secondary || !bson.containsKey(IS_REPLICA_SET_FIELD.getFieldName())
|| !bson.get(IS_REPLICA_SET_FIELD.getFieldName()).isBoolean() || !bson.get(
IS_REPLICA_SET_FIELD.getFieldName()).asBoolean().getValue()) {
throw new FailedToParseException("Expected presence of \""
+ INFO_FIELD + "\" field to indicate no valid "
+ "config loaded, but other fields weren't as we "
+ "expected");
}
return NOT_CONFIGURED;
} else if (bson.containsKey(IS_REPLICA_SET_FIELD.getFieldName())) {
throw new FailedToParseException("Found \"" + IS_REPLICA_SET_FIELD
+ "\" field which should indicate that no valid config "
+ "is loaded, but we didn't also have an \"" + INFO_FIELD
+ "\" field as we expected"
);
}
String setName = BsonReaderTool.getString(bson, SET_NAME_FIELD);
int setVersion = BsonReaderTool.getNumeric(bson, SET_VERSION_FIELD).intValue();
ImmutableList<HostAndPort> hosts = fromBsonArray(bson, HOSTS_FIELD);
ImmutableList<HostAndPort> passives = fromBsonArray(bson, PASSIVES_FIELD);
ImmutableList<HostAndPort> arbiters = fromBsonArray(bson, ARBITERS_FIELD);
HostAndPort primary = BsonReaderTool.getHostAndPort(bson, PRIMARY_FIELD, null);
boolean arbiterOnly = BsonReaderTool.getBoolean(bson, ARBITER_ONLY_FIELD, false);
boolean passive = BsonReaderTool.getBoolean(bson, PASSIVE_FIELD, false);
boolean hidden = BsonReaderTool.getBoolean(bson, HIDDEN_FIELD, false);
boolean buildIndexes = BsonReaderTool.getBoolean(bson, BUILD_INDEXES_FIELD, false);
UnsignedInteger slaveDelay = UnsignedInteger.fromIntBits(
BsonReaderTool.getNumeric(bson, SLAVE_DELAY_FIELD, DefaultBsonValues.INT32_ZERO)
.intValue()
);
final ImmutableMap<String, String> tags;
if (!bson.containsKey(TAGS_FIELD.getFieldName())) {
tags = ImmutableMap.of();
} else {
ImmutableMap.Builder<String, String> tagsBuilder = ImmutableMap.builder();
BsonDocument uncastedTags = BsonReaderTool.getDocument(bson, TAGS_FIELD);
for (Entry<?> entry : uncastedTags) {
if (!entry.getValue().isString()) {
throw new TypesMismatchException(
entry.getKey(),
"string",
entry.getValue().getType(),
"Elements in \"" + TAGS_FIELD + "\" obj of "
+ "isMaster response must be of type string "
+ " but found type " + entry.getValue().getType().toString()
.toLowerCase(Locale.ROOT)
);
}
String tagValue = uncastedTags.get(entry.getKey()).asString().getValue();
tagsBuilder.put(entry.getKey(), tagValue);
}
tags = tagsBuilder.build();
}
BsonObjectId electionId = BsonReaderTool.getObjectId(bson, ELECTION_ID_FIELD, null);
HostAndPort me = BsonReaderTool.getHostAndPort(bson, ME_FIELD, null);
return new IsMasterReply(
master,
secondary,
setName,
setVersion,
hosts,
passives,
arbiters,
primary,
arbiterOnly,
passive,
hidden,
buildIndexes,
slaveDelay,
tags,
me,
electionId,
BsonReaderTool.getInteger(bson, MAX_BSON_OBJECT_SIZE),
BsonReaderTool.getInteger(bson, MAX_MESSAGE_SIZE_BYTES),
BsonReaderTool.getInteger(bson, MAX_WRITE_BATCH_SIZE),
BsonReaderTool.getInstant(bson, LOCAL_TIME),
BsonReaderTool.getInteger(bson, MAX_WIRE_VERSION),
BsonReaderTool.getInteger(bson, MIN_WIRE_VERSION)
);
}
public static class Builder {
private MemberState myState;
private String setName;
private Integer setVersion;
private final List<HostAndPort> hosts = new ArrayList<>();
private final List<HostAndPort> passives = new ArrayList<>();
private final List<HostAndPort> arbiters = new ArrayList<>();
private HostAndPort primary;
private Boolean arbiterOnly;
private Boolean passive;
private Boolean hidden;
private Boolean buildIndexes;
private UnsignedInteger slaveDelay;
private final Map<String, String> tags = new HashMap<>();
private HostAndPort me;
private BsonObjectId electionId;
private boolean built = false;
private int maxBsonObjectSize;
private int maxMessageSizeBytes;
private int maxWriteBatchSize;
private Instant localTime;
private int maxWireVersion;
private int minWireVersion;
public Builder() {
}
public Builder(IsMasterReply other) {
if (other.master) {
this.myState = MemberState.RS_PRIMARY;
} else if (other.secondary) {
this.myState = MemberState.RS_SECONDARY;
}
this.setName = other.setName;
this.setVersion = other.setVersion;
this.hosts.addAll(other.hosts);
this.passives.addAll(other.passives);
this.arbiters.addAll(other.arbiters);
this.primary = other.primary;
this.arbiterOnly = other.arbiterOnly;
this.passive = other.passive;
this.hidden = other.hidden;
this.buildIndexes = other.buildIndexes;
this.slaveDelay = other.slaveDelay;
this.tags.putAll(other.tags);
this.me = other.me;
this.electionId = other.electionId;
}
public static Builder fromStandalone(
int maxBsonObjectSize,
int maxMessageSizeBytes,
int maxWriteBatchSize,
Instant localTime,
int maxWireVersion,
int minWireVersion) {
return new Builder()
.setMyState(MemberState.RS_PRIMARY)
.setMaxBsonObjectSize(maxBsonObjectSize)
.setMaxMessageSizeBytes(maxMessageSizeBytes)
.setMaxWriteBatchSize(maxWriteBatchSize)
.setLocalTime(localTime)
.setMaxWireVersion(maxWireVersion)
.setMinWireVersion(minWireVersion);
}
public Builder setMyState(MemberState myState) {
Preconditions.checkState(!built);
this.myState = myState;
return this;
}
public Builder setReplSetName(String setName) {
Preconditions.checkState(!built);
this.setName = setName;
return this;
}
public Builder setReplSetVersion(Integer setVersion) {
Preconditions.checkState(!built);
this.setVersion = setVersion;
return this;
}
public Builder addHost(HostAndPort host) {
Preconditions.checkState(!built);
this.hosts.add(host);
return this;
}
public Builder addPassive(HostAndPort passive) {
Preconditions.checkState(!built);
this.passives.add(passive);
return this;
}
public Builder addArbiter(HostAndPort arbiter) {
Preconditions.checkState(!built);
this.arbiters.add(arbiter);
return this;
}
public Builder setPrimary(HostAndPort primary) {
Preconditions.checkState(!built);
this.primary = primary;
return this;
}
public Builder setArbiterOnly(Boolean arbiterOnly) {
Preconditions.checkState(!built);
this.arbiterOnly = arbiterOnly;
return this;
}
public Builder setPassive(Boolean passive) {
Preconditions.checkState(!built);
this.passive = passive;
return this;
}
public Builder setHidden(Boolean hidden) {
Preconditions.checkState(!built);
this.hidden = hidden;
return this;
}
public Builder setBuildIndexes(Boolean buildIndexes) {
Preconditions.checkState(!built);
this.buildIndexes = buildIndexes;
return this;
}
public Builder setSlaveDelay(UnsignedInteger slaveDelay) {
Preconditions.checkState(!built);
this.slaveDelay = slaveDelay;
return this;
}
public Builder addTag(String key, String tag) {
Preconditions.checkState(!built);
this.tags.put(key, tag);
return this;
}
public Builder setMe(HostAndPort me) {
Preconditions.checkState(!built);
this.me = me;
return this;
}
public Builder setElectionId(BsonObjectId electionId) {
Preconditions.checkState(!built);
this.electionId = electionId;
return this;
}
public Builder setMaxBsonObjectSize(int maxBsonObjectSize) {
Preconditions.checkState(!built);
this.maxBsonObjectSize = maxBsonObjectSize;
return this;
}
public Builder setMaxMessageSizeBytes(int maxMessageSizeBytes) {
Preconditions.checkState(!built);
this.maxMessageSizeBytes = maxMessageSizeBytes;
return this;
}
public Builder setMaxWriteBatchSize(int maxWriteBatchSize) {
Preconditions.checkState(!built);
this.maxWriteBatchSize = maxWriteBatchSize;
return this;
}
public Builder setLocalTime(Instant localTime) {
Preconditions.checkState(!built);
this.localTime = localTime;
return this;
}
public Builder setMaxWireVersion(int maxWireVersion) {
Preconditions.checkState(!built);
this.maxWireVersion = maxWireVersion;
return this;
}
public Builder setMinWireVersion(int minWireVersion) {
Preconditions.checkState(!built);
this.minWireVersion = minWireVersion;
return this;
}
public IsMasterReply build() {
Preconditions.checkState(!built);
built = true;
return new IsMasterReply(
myState == MemberState.RS_PRIMARY,
myState == MemberState.RS_SECONDARY,
setName,
setVersion,
hosts,
passives,
arbiters,
primary,
arbiterOnly,
passive,
hidden,
buildIndexes,
slaveDelay,
tags,
me,
electionId,
maxBsonObjectSize,
maxMessageSizeBytes,
maxWriteBatchSize,
localTime,
maxWireVersion,
minWireVersion
);
}
}
}
}