/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.client.file.policy;
import alluxio.client.block.BlockWorkerInfo;
import alluxio.client.block.policy.BlockLocationPolicy;
import alluxio.client.block.policy.options.GetWorkerOptions;
import alluxio.wire.WorkerNetAddress;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
/**
* A policy that chooses the worker for the next block in a round-robin manner and skips workers
* that do not have enough space. The policy returns null if no worker can be found.
*/
// TODO(peis): Move the BlockLocationPolicy implementation to alluxio.client.block.policy.
@NotThreadSafe
public final class RoundRobinPolicy implements FileWriteLocationPolicy, BlockLocationPolicy {
private List<BlockWorkerInfo> mWorkerInfoList;
private int mIndex;
private boolean mInitialized = false;
/** This caches the {@link WorkerNetAddress} for the block IDs.*/
private final HashMap<Long, WorkerNetAddress> mBlockLocationCache = new HashMap<>();
/**
* Constructs a new {@link RoundRobinPolicy}.
*/
public RoundRobinPolicy() {}
/**
* The policy uses the first fetch of worker info list as the base, and visits each of them in a
* round-robin manner in the subsequent calls. The policy doesn't assume the list of worker info
* in the subsequent calls has the same order from the first, and it will skip the workers that
* are no longer active.
*
* @param workerInfoList the info of the active workers
* @param blockSizeBytes the size of the block in bytes
* @return the address of the worker to write to
*/
@Override
public WorkerNetAddress getWorkerForNextBlock(Iterable<BlockWorkerInfo> workerInfoList,
long blockSizeBytes) {
if (!mInitialized) {
mWorkerInfoList = Lists.newArrayList(workerInfoList);
Collections.shuffle(mWorkerInfoList);
mIndex = 0;
mInitialized = true;
}
// at most try all the workers
for (int i = 0; i < mWorkerInfoList.size(); i++) {
WorkerNetAddress candidate = mWorkerInfoList.get(mIndex).getNetAddress();
BlockWorkerInfo workerInfo = findBlockWorkerInfo(workerInfoList, candidate);
mIndex = (mIndex + 1) % mWorkerInfoList.size();
if (workerInfo != null && workerInfo.getCapacityBytes() >= blockSizeBytes) {
return candidate;
}
}
return null;
}
@Override
public WorkerNetAddress getWorker(GetWorkerOptions options) {
WorkerNetAddress address = mBlockLocationCache.get(options.getBlockId());
if (address != null) {
return address;
}
address = getWorkerForNextBlock(options.getBlockWorkerInfos(), options.getBlockSize());
mBlockLocationCache.put(options.getBlockId(), address);
return address;
}
/**
* @param workerInfoList the list of worker info
* @param address the address to look for
* @return the worker info in the list that matches the host name, null if not found
*/
private BlockWorkerInfo findBlockWorkerInfo(Iterable<BlockWorkerInfo> workerInfoList,
WorkerNetAddress address) {
for (BlockWorkerInfo info : workerInfoList) {
if (info.getNetAddress().equals(address)) {
return info;
}
}
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof RoundRobinPolicy)) {
return false;
}
RoundRobinPolicy that = (RoundRobinPolicy) o;
return Objects.equal(mWorkerInfoList, that.mWorkerInfoList)
&& Objects.equal(mIndex, that.mIndex)
&& Objects.equal(mInitialized, that.mInitialized)
&& Objects.equal(mBlockLocationCache, that.mBlockLocationCache);
}
@Override
public int hashCode() {
return Objects.hashCode(mWorkerInfoList, mIndex, mInitialized, mBlockLocationCache);
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("workerInfoList", mWorkerInfoList)
.add("index", mIndex)
.add("initialized", mInitialized)
.add("blockLocationCache", mBlockLocationCache)
.toString();
}
}