/*
* Copyright 2013 LinkedIn, Inc
*
* 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 voldemort.client.rebalance;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import voldemort.VoldemortException;
import voldemort.cluster.Cluster;
import voldemort.serialization.json.JsonReader;
import voldemort.serialization.json.JsonWriter;
import voldemort.utils.Utils;
import voldemort.xml.ClusterMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* Rebalance Task info maintains all information needed for rebalancing for a
* stealer-donor node tuple.
*
* @param stealerNodeId Stealer node id
* @param donorId Donor node id
* @param storeToPartitionList Map of store name to partitions
* @param initialCluster We require the state of the current metadata in order to determine
* correct key movement for RW stores. Otherwise we move keys on the basis of the
* updated metadata and hell breaks loose.
*
* TODO: Verify if initialCluster is needed as part of this class.
*/
public class RebalanceTaskInfo {
private final int stealerId;
private final int donorId;
private HashMap<String, List<Integer>> storeToPartitionIds;
private Cluster initialCluster;
public RebalanceTaskInfo(int stealerNodeId, int donorId, HashMap<String,
List<Integer>> storeToPartitionIds, Cluster initialCluster) {
this.stealerId = stealerNodeId;
this.donorId = donorId;
this.storeToPartitionIds = storeToPartitionIds;
this.initialCluster = initialCluster;
}
public static RebalanceTaskInfo create(String line) {
try {
JsonReader reader = new JsonReader(new StringReader(line));
Map<String, ?> map = reader.readObject();
return create(map);
} catch (Exception e) {
throw new VoldemortException("Failed to create partition info from string: " + line, e);
}
}
// TODO : Need to think through which of these "SerDe" methods are needed for on-the-wire and
// which are needed for on-disk. Neither seems like standard "SerDe". And then come up with a
// standard way to perform Serialization/De-serialization
public static RebalanceTaskInfo create(Map<?, ?> map) {
int stealerId = (Integer)map.get("stealerId");
int donorId = (Integer)map.get("donorId");
List<String> unbalancedStoreList = Utils.uncheckedCast(map.get("unbalancedStores"));
Cluster initialCluster = new ClusterMapper()
.readCluster(new StringReader((String)map.get("initialCluster")));
HashMap<String, List<Integer>> storeToPartitionIds = Maps.newHashMap();
for (String unbalancedStore : unbalancedStoreList) {
List<Integer> partitionList = Utils.uncheckedCast(map.get(unbalancedStore + "partitionList"));
if (partitionList.size() > 0)
storeToPartitionIds.put(unbalancedStore, partitionList);
}
return new RebalanceTaskInfo(stealerId, donorId, storeToPartitionIds, initialCluster);
}
public synchronized ImmutableMap<String, Object> asMap() {
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<String, Object>();
builder.put("stealerId", stealerId)
.put("donorId", donorId)
.put("unbalancedStores", Lists.newArrayList(storeToPartitionIds.keySet()))
.put("initialCluster", new ClusterMapper().writeCluster(initialCluster));
for (String unbalancedStore : storeToPartitionIds.keySet()) {
List<Integer> partitionIds = storeToPartitionIds.get(unbalancedStore);
if (!partitionIds.isEmpty())
builder.put(unbalancedStore + "partitionList", partitionIds);
else
builder.put(unbalancedStore + "partitionList" , Lists.newArrayList());
}
return builder.build();
}
/**
* @return store to partition ids map
*/
public synchronized HashMap<String, List<Integer>> getStoreToPartitionIds() {
return this.storeToPartitionIds;
}
public synchronized void setPartitionIds(String storeName, List<Integer> partitionIds) {
this.storeToPartitionIds.put(storeName, partitionIds);
}
public synchronized void setStoreToPartitionList(HashMap<String, List<Integer>> storeToPartitionIds) {
this.storeToPartitionIds = storeToPartitionIds;
}
/**
* @return initial cluster
*/
public synchronized Cluster getInitialCluster() {
return initialCluster;
}
/**
* @return returns donor node id
*/
public synchronized int getDonorId() {
return donorId;
}
/**
* @return returns stealer node id
*/
public synchronized int getStealerId() {
return stealerId;
}
/**
* Total count of partition-stores moved in this task.
*
* @return number of partition stores moved in this task.
*/
public synchronized int getPartitionStoreMoves() {
int count = 0;
for (List<Integer> entry : storeToPartitionIds.values())
count += entry.size();
return count;
}
/**
* Removes the store name from the map.
*
* @param storeName name to be removed from the map
*/
public synchronized void removeStore(String storeName) {
this.storeToPartitionIds.remove(storeName);
}
/**
* Returns the list of partitions ids corresponding to a store.
*
* @param storeName name of the store
* @return list of partitions ids for the store.
*/
public synchronized List<Integer> getPartitionIds(String storeName) {
return this.storeToPartitionIds.get(storeName);
}
/**
* Returns all the store names from the map
*
* @return returns a list of stores in the map
*/
public synchronized Set<String> getPartitionStores() {
return this.storeToPartitionIds.keySet();
}
/**
* Returns the total count of partitions across all stores.
*
* @return returns the total count of partitions across all stores.
*/
public synchronized int getPartitionStoreCount() {
int count = 0;
for (String store : storeToPartitionIds.keySet()) {
count += storeToPartitionIds.get(store).size();
}
return count;
}
@Override
public synchronized String toString() {
StringBuffer sb = new StringBuffer();
sb.append("\nRebalanceTaskInfo(" + getStealerId()
+ " [" + initialCluster.getNodeById(getStealerId()).getHost()
+ "] <--- " + getDonorId()
+ " ["
+ initialCluster.getNodeById(getDonorId()).getHost()
+ "] ");
for (String unbalancedStore : storeToPartitionIds.keySet()) {
sb.append("\n\t- Store '" + unbalancedStore + "' move ");
List<Integer> partitionIds = storeToPartitionIds.get(unbalancedStore);
if (!partitionIds.isEmpty())
sb.append(" - " + partitionIds);
else
sb.append(" - []");
}
sb.append(")");
return sb.toString();
}
/**
* Pretty prints a task list of rebalancing tasks.
*
* @param infos list of rebalancing tasks (RebalancePartitiosnInfo)
* @return pretty-printed string
*/
public static String taskListToString(List<RebalanceTaskInfo> infos) {
StringBuffer sb = new StringBuffer();
for (RebalanceTaskInfo info : infos) {
sb.append("\t").append(info.getDonorId()).append(" -> ").append(info.getStealerId()).append(" : [");
for (String storeName : info.getPartitionStores()) {
sb.append("{").append(storeName).append(" : ").append(info.getPartitionIds(storeName)).append("}");
}
sb.append("]").append(Utils.NEWLINE);
}
return sb.toString();
}
public synchronized String toJsonString() {
Map<String, Object> map = asMap();
StringWriter writer = new StringWriter();
new JsonWriter(writer).write(map);
writer.flush();
return writer.toString();
}
@Override
public synchronized boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
RebalanceTaskInfo that = (RebalanceTaskInfo)o;
if (donorId != that.donorId)
return false;
if (stealerId != that.stealerId)
return false;
if (!initialCluster.equals(that.initialCluster))
return false;
if (storeToPartitionIds != null ? !storeToPartitionIds.equals(that.storeToPartitionIds)
: that.storeToPartitionIds != null)
return false;
return true;
}
@Override
public synchronized int hashCode() {
int result = stealerId;
result = 31 * result + donorId;
result = 31 * result + initialCluster.hashCode();
result = 31 * result + (storeToPartitionIds != null ? storeToPartitionIds.hashCode() : 0);
return result;
}
}