/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* 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 VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb;
import com.google_voltpatches.common.collect.ImmutableMap;
import com.google_voltpatches.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
public class DRRoleStats extends StatsSource {
public static final String CN_ROLE = "ROLE";
public static final String CN_STATE = "STATE";
public static final String CN_REMOTE_CLUSTER_ID = "REMOTE_CLUSTER_ID";
public enum State {
DISABLED, // Feature is completely disabled
ACTIVE, // Actively exchanging data with remote cluster
PENDING, // Waiting to establish connection with remote cluster
STOPPED; // Replication broken due to error
/**
* Almost like logically ANDing two states together. The precedence is
* STOPPED->PENDING->ACTIVE->DISABLED. This happened to be the reverse
* ordinal order.
*
* @param other The other state
* @return The combined state
*/
public State and(State other)
{
if (other == null) {
return this;
}
if (other.ordinal() > this.ordinal()) {
return other;
} else {
return this;
}
}
}
private final VoltDBInterface m_vdb;
public DRRoleStats(VoltDBInterface vdb)
{
super(false);
m_vdb = vdb;
}
@Override
protected void populateColumnSchema(ArrayList<VoltTable.ColumnInfo> columns)
{
columns.add(new VoltTable.ColumnInfo(CN_ROLE, VoltType.STRING));
columns.add(new VoltTable.ColumnInfo(CN_STATE, VoltType.STRING));
columns.add(new VoltTable.ColumnInfo(CN_REMOTE_CLUSTER_ID, VoltType.INTEGER));
}
@Override
protected void updateStatsRow(Object rowKey, Object[] rowValues)
{
@SuppressWarnings("unchecked") Map.Entry<Byte, State> state = (Map.Entry<Byte, State>) rowKey;
final String role = getRole();
rowValues[columnNameToIndex.get(CN_ROLE)] = role;
rowValues[columnNameToIndex.get(CN_STATE)] = state.getValue().name();
rowValues[columnNameToIndex.get(CN_REMOTE_CLUSTER_ID)] = state.getKey();
}
@Override
protected Iterator<Object> getStatsRowKeyIterator(boolean interval)
{
final ProducerDRGateway producer = m_vdb.getNodeDRGateway();
final Map<Byte, DRProducerNodeStats> producerStats;
if (producer != null) {
producerStats = producer.getNodeDRStats();
} else {
producerStats = ImmutableMap.of((byte) -1, DRProducerNodeStats.DISABLED_NODE_STATS);
}
final ConsumerDRGateway consumer = m_vdb.getConsumerDRGateway();
final Map<Byte, State> consumerStates;
if (consumer != null) {
consumerStates = consumer.getStates();
} else {
consumerStates = ImmutableMap.of((byte) -1, State.DISABLED);
}
final Map<Byte, State> states = mergeProducerConsumerStates(producerStats, consumerStates);
final Iterator<Map.Entry<Byte, State>> iter = states.entrySet().iterator();
return new Iterator<Object>() {
@Override
public boolean hasNext()
{
return iter.hasNext();
}
@Override
public Object next()
{
return iter.next();
}
};
}
private String getRole()
{
return m_vdb.getCatalogContext().cluster.getDrrole().toUpperCase();
}
private static Map<Byte, State> mergeProducerConsumerStates(Map<Byte, DRProducerNodeStats> producerStats,
Map<Byte, State> consumerStates)
{
Map<Byte, State> states = new HashMap<>();
for (byte clusterId : Sets.union(producerStats.keySet(), consumerStates.keySet())) {
final DRProducerNodeStats producerNodeStats = producerStats.get(clusterId);
final State consumerState = consumerStates.get(clusterId);
State finalState = State.DISABLED;
if (producerNodeStats != null) {
finalState = finalState.and(producerNodeStats.state);
}
if (consumerState != null) {
finalState = finalState.and(consumerState);
}
states.put(clusterId, finalState);
}
// Remove the -1 placeholder if there are real cluster states
if (states.size() > 1) {
states.remove((byte) -1);
}
return states;
}
/**
* Aggregates DRROLE statistics reported by multiple nodes into a single
* cluster-wide row. The role column should be the same across all
* nodes. The state column may defer slightly and it uses the same logical
* AND-ish operation to combine the states.
*
* This method modifies the VoltTable in place.
* @param stats Statistics from all cluster nodes. This will be modified in
* place. Cannot be null.
* @return The same VoltTable as in the parameter.
* @throws IllegalArgumentException If the cluster nodes don't agree on the
* DR role.
*/
public static VoltTable aggregateStats(VoltTable stats) throws IllegalArgumentException
{
stats.resetRowPosition();
if (stats.getRowCount() == 0) {
return stats;
}
String role = null;
Map<Byte, State> states = new TreeMap<>();
while (stats.advanceRow()) {
final byte clusterId = (byte) stats.getLong(CN_REMOTE_CLUSTER_ID);
final String curRole = stats.getString(CN_ROLE);
if (role == null) {
role = curRole;
} else if (!role.equals(curRole)) {
throw new IllegalArgumentException("Inconsistent DR role across cluster nodes: " + stats.toFormattedString(false));
}
final State state = State.valueOf(stats.getString(CN_STATE));
states.put(clusterId, state.and(states.get(clusterId)));
}
assert role != null;
stats.clearRowData();
for (Map.Entry<Byte, State> e : states.entrySet()) {
stats.addRow(role, e.getValue().name(), e.getKey());
}
return stats;
}
}