/* * 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.bson.BsonDocument; import com.eightkdata.mongowp.bson.BsonDocument.Entry; import com.eightkdata.mongowp.bson.BsonInt64; import com.eightkdata.mongowp.bson.BsonValue; import com.eightkdata.mongowp.bson.utils.DefaultBsonValues; import com.eightkdata.mongowp.exceptions.BadValueException; import com.eightkdata.mongowp.exceptions.NoSuchKeyException; import com.eightkdata.mongowp.exceptions.TypesMismatchException; import com.eightkdata.mongowp.fields.BooleanField; import com.eightkdata.mongowp.fields.DocField; import com.eightkdata.mongowp.fields.DoubleField; import com.eightkdata.mongowp.fields.HostAndPortField; import com.eightkdata.mongowp.fields.IntField; import com.eightkdata.mongowp.fields.LongField; import com.eightkdata.mongowp.utils.BsonDocumentBuilder; import com.eightkdata.mongowp.utils.BsonReaderTool; import com.google.common.collect.ImmutableMap; import com.google.common.net.HostAndPort; import java.time.Duration; import java.util.Map; import javax.annotation.concurrent.Immutable; /** * Replication configuration about a particular member of a replica set. */ @Immutable public class MemberConfig { public static final HostAndPortField HOST_FIELD = new HostAndPortField("host"); public static final BooleanField ARBITER_ONLY_FIELD = new BooleanField("arbiterOnly"); public static final BooleanField BUILD_INDEXES_FIELD = new BooleanField("buildIndexes"); public static final BooleanField HIDDEN_FIELD = new BooleanField("hidden"); public static final DoubleField PRIORITY_FIELD = new DoubleField("priority"); public static final DocField TAGS_FIELD = new DocField("tags"); public static final LongField SLAVE_DELAY_FIELD = new LongField("slaveDelay"); public static final IntField VOTES_FIELD = new IntField("votes"); public static final IntField ID_FIELD = new IntField("_id"); private static final long MAX_SLAVE_DELAY = 3600 * 24 * 366; private static final int DEFAULT_VOTES = 1; private static final double DEFAULT_PRIORITY = 1; private static final boolean DEFAULT_ARBITER_ONLY = false; private static final BsonInt64 DEFAULT_SLAVE_DELAY = DefaultBsonValues.newLong(0); private static final boolean DEFAULT_HIDDEN = false; private static final boolean DEFAULT_BUILD_INDEXES = true; private final int id; private final HostAndPort host; private final double priority; private final int votes; private final boolean arbiterOnly; /** * In seconds */ private final long slaveDelay; private final boolean hidden; private final boolean buildIndexes; private final ImmutableMap<String, String> tags; public MemberConfig(int id, HostAndPort host, double priority, int votes, boolean arbiterOnly, long slaveDelay, boolean hidden, boolean buildIndexes, ImmutableMap<String, String> tags) { this.id = id; this.host = host; this.priority = priority; this.votes = votes; this.arbiterOnly = arbiterOnly; this.slaveDelay = slaveDelay; this.hidden = hidden; this.buildIndexes = buildIndexes; this.tags = tags; } /** * @return the id of this member on the configuration. On a valid MemberConfig, this value is * always on [0, 255] */ public int getId() { return id; } public HostAndPort getHostAndPort() { return host; } public double getPriority() { return priority; } public int getNumVotes() { return votes; } public boolean isVoter() { return votes != 0; } public boolean isArbiter() { return arbiterOnly; } public boolean isElectable() { return !isArbiter() && getPriority() > 0; } public long getSlaveDelay() { return slaveDelay; } public Duration getSlaveDelayDuration() { return Duration.ofSeconds(slaveDelay); } public boolean isHidden() { return hidden; } public boolean buildIndexes() { return buildIndexes; } public ImmutableMap<String, String> getTags() { return tags; } public static MemberConfig fromDocument(BsonDocument bson) throws TypesMismatchException, NoSuchKeyException, BadValueException { int id = BsonReaderTool.getNumeric(bson, "_id").intValue(); HostAndPort host = BsonReaderTool.getHostAndPort(bson, "host"); Builder builder = new Builder(id, host) .setVotes(BsonReaderTool.getInteger(bson, "votes", DEFAULT_VOTES)) .setPriority(BsonReaderTool.getDouble(bson, "priority", DEFAULT_PRIORITY)) .setArbiterOnly(BsonReaderTool .getBooleanOrNumeric(bson, "arbiterOnly", DEFAULT_ARBITER_ONLY)) .setSlaveDelay(BsonReaderTool.getNumeric(bson, "slaveDelay", DEFAULT_SLAVE_DELAY) .longValue()) .setHidden(BsonReaderTool.getBooleanOrNumeric(bson, "hidden", DEFAULT_HIDDEN)) .setBuildIndexes(BsonReaderTool.getBooleanOrNumeric(bson, "buildIndexes", DEFAULT_BUILD_INDEXES)); BsonDocument castedTags = BsonReaderTool.getDocument(bson, "tags"); for (Entry entry : castedTags) { BsonValue value = entry.getValue(); if (value.isString()) { throw new TypesMismatchException(entry.getKey(), "string", value.getType()); } String castedValue = value.asString().getValue(); builder.putTag(entry.getKey(), castedValue); } return builder.build(); } public static class Builder { private final int id; private final HostAndPort host; private int votes = DEFAULT_VOTES; private double priority = DEFAULT_PRIORITY; private boolean arbiterOnly = DEFAULT_ARBITER_ONLY; private long slaveDelay = DEFAULT_SLAVE_DELAY.longValue(); private boolean hidden = DEFAULT_HIDDEN; private boolean buildIndexes = DEFAULT_BUILD_INDEXES; private final ImmutableMap.Builder<String, String> tagsBuilder = ImmutableMap.builder(); public Builder(int id, HostAndPort host) { super(); this.id = id; this.host = host; } public Builder setVotes(int votes) { this.votes = votes; return this; } public Builder setPriority(double priority) { this.priority = priority; return this; } public Builder setArbiterOnly(boolean arbiterOnly) { this.arbiterOnly = arbiterOnly; return this; } public Builder setSlaveDelay(long slaveDelay) { this.slaveDelay = slaveDelay; return this; } public Builder setHidden(boolean hidden) { this.hidden = hidden; return this; } public Builder setBuildIndexes(boolean buildIndexes) { this.buildIndexes = buildIndexes; return this; } public Builder putTag(String key, String value) { this.tagsBuilder.put(key, value); return this; } public Builder putAllTags(Map<String, String> map) { this.tagsBuilder.putAll(map); return this; } public MemberConfig build() { return new MemberConfig(id, host, priority, votes, arbiterOnly, slaveDelay, hidden, buildIndexes, tagsBuilder.build()); } } public void validate() throws BadValueException { if (id < 0 || id > 255) { throw new BadValueException(ID_FIELD + " field value of " + id + " is out of range."); } if (priority < 0 || priority > 1000) { throw new BadValueException(PRIORITY_FIELD + " field value of " + priority + " is out of range"); } if (votes != 0 && votes != 1) { throw new BadValueException(VOTES_FIELD + " field value is " + votes + " but must be 0 or 1"); } if (arbiterOnly) { if (!tags.isEmpty()) { throw new BadValueException("Cannot set tags on arbiters."); } if (!isVoter()) { throw new BadValueException("Arbiter must vote (cannot have 0 votes)"); } } if (slaveDelay < 0 || slaveDelay > MAX_SLAVE_DELAY) { throw new BadValueException(SLAVE_DELAY_FIELD + " field value of " + slaveDelay + " seconds is out of range"); } if (slaveDelay > 0 && priority != 0) { throw new BadValueException("slaveDelay requires priority be zero"); } if (hidden && priority != 0) { throw new BadValueException("priority must be 0 when hidden=true"); } if (!buildIndexes && priority != 0) { throw new BadValueException("priority must be 0 when buildIndexes=false"); } } public BsonDocument toBson() { BsonDocumentBuilder object = new BsonDocumentBuilder(); object.append(ID_FIELD, id); object.append(HOST_FIELD, host); object.append(ARBITER_ONLY_FIELD, arbiterOnly); object.append(BUILD_INDEXES_FIELD, buildIndexes); object.append(HIDDEN_FIELD, hidden); object.append(PRIORITY_FIELD, priority); BsonDocumentBuilder tagsDoc = new BsonDocumentBuilder(); for (java.util.Map.Entry<String, String> entry : tags.entrySet()) { tagsDoc.appendUnsafe(entry.getKey(), DefaultBsonValues.newString(entry.getValue())); } object.append(TAGS_FIELD, tagsDoc.build()); object.append(SLAVE_DELAY_FIELD, slaveDelay); object.append(VOTES_FIELD, votes); return object.build(); } /** * Returns true iff this member contais at least one non internal tag */ public boolean hasTags() { for (java.util.Map.Entry<String, String> entry : getTags().entrySet()) { String tagKey = entry.getKey(); if (tagKey.charAt(0) == '$') { // Filter out internal tags continue; } return true; } return false; } }