/** * 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.apache.aurora.common.zookeeper; import java.net.InetSocketAddress; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import org.apache.aurora.common.base.ExceptionalCommand; import org.apache.aurora.common.zookeeper.Candidate.Leader; import org.apache.aurora.common.zookeeper.Group.JoinException; import org.apache.zookeeper.data.ACL; public class SingletonServiceImpl implements SingletonService { @VisibleForTesting static final String LEADER_ELECT_NODE_PREFIX = "singleton_candidate_"; /** * Creates a candidate that can be combined with an existing server set to form a singleton * service using {@link #SingletonServiceImpl(ServerSet, Candidate)}. * * @param zkClient The ZooKeeper client to use. * @param servicePath The path where service nodes live. * @param acl The acl to apply to newly created candidate nodes and serverset nodes. * @return A candidate that can be housed with a standard server set under a single zk path. */ public static Candidate createSingletonCandidate( ZooKeeperClient zkClient, String servicePath, Iterable<ACL> acl) { return new CandidateImpl(new Group(zkClient, acl, servicePath, LEADER_ELECT_NODE_PREFIX)); } private final ServerSet serverSet; private final Candidate candidate; /** * Creates a new singleton service that uses the supplied candidate to vie for leadership and then * advertises itself in the given server set once elected. * * @param serverSet The server set to advertise in on election. * @param candidate The candidacy to use to vie for election. */ public SingletonServiceImpl(ServerSet serverSet, Candidate candidate) { this.serverSet = Preconditions.checkNotNull(serverSet); this.candidate = Preconditions.checkNotNull(candidate); } @Override public void lead(final InetSocketAddress endpoint, final Map<String, InetSocketAddress> additionalEndpoints, final LeadershipListener listener) throws LeadException, InterruptedException { Preconditions.checkNotNull(listener); try { candidate.offerLeadership(new Leader() { @Override public void onElected(final ExceptionalCommand<JoinException> abdicate) { listener.onLeading(new LeaderControl() { ServerSet.EndpointStatus endpointStatus = null; final AtomicBoolean left = new AtomicBoolean(false); // Methods are synchronized to prevent simultaneous invocations. @Override public synchronized void advertise() throws AdvertiseException, InterruptedException { Preconditions.checkState(!left.get(), "Cannot advertise after leaving."); Preconditions.checkState(endpointStatus == null, "Cannot advertise more than once."); try { endpointStatus = serverSet.join(endpoint, additionalEndpoints); } catch (JoinException e) { throw new AdvertiseException("Problem advertising endpoint " + endpoint, e); } } @Override public synchronized void leave() throws LeaveException { Preconditions.checkState(left.compareAndSet(false, true), "Cannot leave more than once."); if (endpointStatus != null) { try { endpointStatus.leave(); } catch (ServerSet.UpdateException e) { throw new LeaveException("Problem updating endpoint status for abdicating leader " + "at endpoint " + endpoint, e); } } try { abdicate.execute(); } catch (JoinException e) { throw new LeaveException("Problem abdicating leadership for endpoint " + endpoint, e); } } }); } @Override public void onDefeated() { listener.onDefeated(); } }); } catch (JoinException e) { throw new LeadException("Problem joining leadership group for endpoint " + endpoint, e); } catch (Group.WatchException e) { throw new LeadException("Problem getting initial membership list for leadership group.", e); } } }