/**
* Copyright 2011 LiveRamp
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.liveramp.hank.ring_group_conductor;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.log4j.PropertyConfigurator;
import com.liveramp.hank.config.RingGroupConductorConfigurator;
import com.liveramp.hank.config.yaml.YamlRingGroupConductorConfigurator;
import com.liveramp.hank.coordinator.Coordinator;
import com.liveramp.hank.coordinator.RingGroup;
import com.liveramp.hank.partition_assigner.RendezVousPartitionAssigner;
import com.liveramp.hank.util.CommandLineChecker;
public class RingGroupConductor {
private static final Logger LOG = LoggerFactory.getLogger(RingGroupConductor.class);
private final RingGroupConductorConfigurator configurator;
private final String ringGroupName;
private final Coordinator coordinator;
private final Object lock = new Object();
private RingGroup ringGroup;
private final RingGroupUpdateTransitionFunction transFunc;
private boolean stopping = false;
private boolean claimedRingGroupConductor;
private Thread shutdownHook;
public RingGroupConductor(RingGroupConductorConfigurator configurator) throws IOException {
this(configurator, new RingGroupUpdateTransitionFunctionImpl(new RendezVousPartitionAssigner(),
configurator.getMinRingFullyServingObservations(),
configurator.getMinServingReplicas(),
configurator.getAvailabilityBucketMinServingReplicas(),
configurator.getHostAvailabilityBucketFlag()
));
}
RingGroupConductor(RingGroupConductorConfigurator configurator, RingGroupUpdateTransitionFunction transFunc) throws IOException {
this.configurator = configurator;
this.transFunc = transFunc;
ringGroupName = configurator.getRingGroupName();
this.coordinator = configurator.createCoordinator();
}
public void run() throws IOException {
// Add shutdown hook
addShutdownHook();
claimedRingGroupConductor = false;
LOG.info("Ring Group Conductor for ring group " + ringGroupName + " starting.");
try {
ringGroup = coordinator.getRingGroup(ringGroupName);
// attempt to claim the ring group conductor title
if (ringGroup.claimRingGroupConductor(configurator.getInitialMode())) {
claimedRingGroupConductor = true;
// loop until we're taken down
stopping = false;
try {
while (!stopping) {
// take a snapshot of the current ring, since it might get changed
// while we're processing the current update.
RingGroup snapshotRingGroup;
synchronized (lock) {
snapshotRingGroup = ringGroup;
}
processUpdates(snapshotRingGroup);
Thread.sleep(configurator.getSleepInterval());
}
} catch (InterruptedException e) {
// daemon is going down.
}
} else {
LOG.info("Attempted to claim Ring Group Conductor status, but there was already a lock in place!");
}
} catch (Throwable t) {
LOG.error("unexpected exception!", t);
} finally {
releaseIfClaimed();
}
LOG.info("Ring Group Conductor for ring group " + ringGroupName + " shutting down.");
// Remove shutdown hook. We don't need it anymore
removeShutdownHook();
}
void processUpdates(RingGroup ringGroup) throws IOException {
// Only process updates if ring group conductor is configured to be active/proactive
if (ringGroup.getRingGroupConductorMode() == RingGroupConductorMode.ACTIVE ||
ringGroup.getRingGroupConductorMode() == RingGroupConductorMode.PROACTIVE) {
transFunc.manageTransitions(ringGroup);
}
}
private void releaseIfClaimed() throws IOException {
if (claimedRingGroupConductor) {
ringGroup.releaseRingGroupConductor();
claimedRingGroupConductor = false;
}
}
// Give up ring group conductor status on VM exit
private void addShutdownHook() {
if (shutdownHook == null) {
shutdownHook = new Thread() {
@Override
public void run() {
try {
releaseIfClaimed();
} catch (IOException e) {
// When VM is exiting and we fail to release ring group conductor status, swallow the exception
}
}
};
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
}
private void removeShutdownHook() {
if (shutdownHook != null) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
CommandLineChecker.check(args, new String[]{"configuration_file_path", "log4j_properties_file_path"}, RingGroupConductor.class);
String configPath = args[0];
String log4jprops = args[1];
RingGroupConductorConfigurator configurator = new YamlRingGroupConductorConfigurator(configPath);
PropertyConfigurator.configure(log4jprops);
new RingGroupConductor(configurator).run();
}
public void stop() {
stopping = true;
}
}