/** * (c) Copyright 2014 WibiData, Inc. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * 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 org.kiji.mapreduce.framework; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import com.datastax.driver.core.Cluster; import com.datastax.driver.core.Host; import com.datastax.driver.core.HostDistance; import com.datastax.driver.core.QueryOptions; import com.datastax.driver.core.Statement; import com.datastax.driver.core.policies.LoadBalancingPolicy; import com.google.common.collect.AbstractIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Always tries hosts in the same order. Useful for querying system tables. */ class ConsistentHostOrderPolicy implements LoadBalancingPolicy { private static final Logger LOG = LoggerFactory.getLogger(ConsistentHostOrderPolicy.class); private final CopyOnWriteArrayList<Host> mLiveHosts = new CopyOnWriteArrayList<Host>(); private final AtomicInteger mIndex = new AtomicInteger(); private QueryOptions mQueryOptions; private volatile boolean mHasLoggedLocalCLUse; /** * Creates a load balancing policy that picks host to query in a round robin * fashion (on all the hosts of the Cassandra cluster). */ public ConsistentHostOrderPolicy() {} @Override public void init(Cluster cluster, Collection<Host> hosts) { this.mLiveHosts.addAll(hosts); this.mQueryOptions = cluster.getConfiguration().getQueryOptions(); this.mIndex.set(new Random().nextInt(Math.max(hosts.size(), 1))); } /** * Return the HostDistance for the provided host. * <p> * This policy consider all nodes as local. This is generally the right * thing to do in a single datacenter deployment. If you use multiple * datacenter, see {@link com.datastax.driver.core.policies.DCAwareRoundRobinPolicy} instead. * * @param host the host of which to return the distance of. * @return the HostDistance to {@code host}. */ @Override public HostDistance distance(Host host) { return HostDistance.LOCAL; } /** * Returns the hosts to use for a new query. * <p> * The returned plan will try each known host of the cluster. Upon each * call to this method, the {@code i}th host of the plans returned will cycle * over all the hosts of the cluster in a round-robin fashion. * * @param loggedKeyspace the keyspace currently logged in on for this * query. * @param statement the query for which to build the plan. * @return a new query plan, i.e. an iterator indicating which host to * try first for querying, which one to use as failover, etc... */ @Override public Iterator<Host> newQueryPlan(String loggedKeyspace, Statement statement) { // We clone mLiveHosts because we want a version of the list that // cannot change concurrently of the query plan iterator (this // would be racy). We use clone() as it don't involve a copy of the // underlying array (and thus we rely on mLiveHosts being a CopyOnWriteArrayList). @SuppressWarnings("unchecked") final List<Host> hosts = (List<Host>) mLiveHosts.clone(); final int startIdx = mIndex.get(); // Overflow protection; not theoretically thread safe but should be good enough if (startIdx > Integer.MAX_VALUE - 10000) { mIndex.set(0); } return new AbstractIterator<Host>() { private int mIdx = startIdx; private int mRemaining = hosts.size(); @Override protected Host computeNext() { if (mRemaining <= 0) { return endOfData(); } mRemaining--; int c = mIdx++ % hosts.size(); if (c < 0) { c += hosts.size(); } return hosts.get(c); } }; } @Override public void onUp(Host host) { mLiveHosts.addIfAbsent(host); } @Override public void onDown(Host host) { mLiveHosts.remove(host); } @Override public void onAdd(Host host) { onUp(host); } @Override public void onRemove(Host host) { onDown(host); } @Override public void onSuspected(Host host) { // Do nothing for this policy - okay to wait and see if the host is really down. } }