// Copyright 2016 Twitter. All rights reserved.
//
// 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 com.twitter.heron.statemgr.localfs;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.twitter.heron.api.generated.TopologyAPI;
import com.twitter.heron.common.basics.FileUtils;
import com.twitter.heron.proto.ckptmgr.CheckpointManager;
import com.twitter.heron.proto.scheduler.Scheduler;
import com.twitter.heron.proto.system.ExecutionEnvironment;
import com.twitter.heron.proto.system.PackingPlans;
import com.twitter.heron.proto.system.PhysicalPlans;
import com.twitter.heron.proto.tmaster.TopologyMaster;
import com.twitter.heron.spi.common.Config;
import com.twitter.heron.spi.common.Key;
import com.twitter.heron.spi.statemgr.Lock;
import com.twitter.heron.spi.statemgr.WatchCallback;
import com.twitter.heron.statemgr.FileSystemStateManager;
public class LocalFileSystemStateManager extends FileSystemStateManager {
private static final Logger LOG = Logger.getLogger(LocalFileSystemStateManager.class.getName());
/**
* Local filesystem implementation of a lock that mimics the file system behavior of the
* distributed lock.
*/
private final class FileSystemLock implements Lock {
private String path;
private FileSystemLock(String path) {
this.path = path;
}
@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
long giveUpAtMillis = System.currentTimeMillis() + unit.toMillis(timeout);
byte[] fileContents = Thread.currentThread().getName().getBytes(Charset.defaultCharset());
while (true) {
try {
if (setData(this.path, fileContents, false).get()) {
return true;
} else if (System.currentTimeMillis() >= giveUpAtMillis) {
return false;
} else {
TimeUnit.SECONDS.sleep(2); // need to pole the filesystem for availability
}
} catch (ExecutionException e) {
// this is thrown when the file exists, which means the lock can't be obtained
}
}
}
@Override
public void unlock() {
deleteNode(this.path, false);
}
}
@Override
public void initialize(Config ipconfig) {
super.initialize(ipconfig);
// By default, we would init the file tree if it is not there
boolean isInitLocalFileTree = LocalFileSystemContext.initLocalFileTree(ipconfig);
if (isInitLocalFileTree && !initTree()) {
throw new IllegalArgumentException("Failed to initialize Local State manager. "
+ "Check rootAddress: " + rootAddress);
}
}
protected boolean initTree() {
for (StateLocation location : StateLocation.values()) {
String dir = getStateDirectory(location);
LOG.fine(String.format("%s directory: %s", location.getName(), dir));
if (!FileUtils.isDirectoryExists(dir) && !FileUtils.createDirectory(dir)) {
return false;
}
}
return true;
}
// Make utils class protected for easy unit testing
protected ListenableFuture<Boolean> setData(String path, byte[] data, boolean overwrite) {
final SettableFuture<Boolean> future = SettableFuture.create();
boolean ret = FileUtils.writeToFile(path, data, overwrite);
future.set(ret);
return future;
}
private ListenableFuture<Boolean> setData(StateLocation location,
String topologyName,
byte[] bytes,
boolean overwrite) {
return setData(getStatePath(location, topologyName), bytes, overwrite);
}
@Override
protected ListenableFuture<Boolean> nodeExists(String path) {
SettableFuture<Boolean> future = SettableFuture.create();
boolean ret = FileUtils.isFileExists(path);
future.set(ret);
return future;
}
@Override
protected ListenableFuture<Boolean> deleteNode(String path, boolean deleteChildrenIfNecessary) {
final SettableFuture<Boolean> future = SettableFuture.create();
boolean ret = true;
if (FileUtils.isFileExists(path)) {
if (!deleteChildrenIfNecessary && FileUtils.hasChildren(path)) {
LOG.severe("delete called on a path with children but deleteChildrenIfNecessary is false: "
+ path);
ret = false;
} else {
ret = FileUtils.deleteFile(path);
}
}
future.set(ret);
return future;
}
@Override
@SuppressWarnings("unchecked") // we don't know what M is until runtime
protected <M extends Message> ListenableFuture<M> getNodeData(WatchCallback watcher,
String path,
Message.Builder builder) {
final SettableFuture<M> future = SettableFuture.create();
byte[] data = new byte[]{};
if (FileUtils.isFileExists(path)) {
data = FileUtils.readFromFile(path);
}
if (data.length == 0) {
future.set(null);
return future;
}
try {
builder.mergeFrom(data);
future.set((M) builder.build());
} catch (InvalidProtocolBufferException e) {
future.setException(new RuntimeException("Could not parse " + Message.Builder.class, e));
}
return future;
}
@Override
public ListenableFuture<Boolean> setExecutionState(
ExecutionEnvironment.ExecutionState executionState, String topologyName) {
return setData(
StateLocation.EXECUTION_STATE, topologyName, executionState.toByteArray(), false);
}
@Override
public ListenableFuture<Boolean> setTMasterLocation(
TopologyMaster.TMasterLocation location, String topologyName) {
// Note: Unlike Zk statemgr, we overwrite the location even if there is already one.
// This is because when running in simulator we control when a tmaster dies and
// comes up deterministically.
return setData(StateLocation.TMASTER_LOCATION, topologyName, location.toByteArray(), true);
}
@Override
public ListenableFuture<Boolean> setMetricsCacheLocation(
TopologyMaster.MetricsCacheLocation location, String topologyName) {
// Note: Unlike Zk statemgr, we overwrite the location even if there is already one.
// This is because when running in simulator we control when a tmaster dies and
// comes up deterministically.
LOG.info("setMetricsCacheLocation: ");
return setData(StateLocation.METRICSCACHE_LOCATION, topologyName, location.toByteArray(), true);
}
@Override
public ListenableFuture<Boolean> setTopology(TopologyAPI.Topology topology, String topologyName) {
return setData(StateLocation.TOPOLOGY, topologyName, topology.toByteArray(), false);
}
@Override
public ListenableFuture<Boolean> setPhysicalPlan(
PhysicalPlans.PhysicalPlan physicalPlan, String topologyName) {
return setData(StateLocation.PHYSICAL_PLAN, topologyName, physicalPlan.toByteArray(), false);
}
@Override
public ListenableFuture<Boolean> setPackingPlan(
PackingPlans.PackingPlan packingPlan, String topologyName) {
return setData(StateLocation.PACKING_PLAN, topologyName, packingPlan.toByteArray(), true);
}
@Override
public ListenableFuture<Boolean> setSchedulerLocation(
Scheduler.SchedulerLocation location, String topologyName) {
// Note: Unlike Zk statemgr, we overwrite the location even if there is already one.
// This is because when running in simulator we control when a scheduler dies and
// comes up deterministically.
return setData(StateLocation.SCHEDULER_LOCATION, topologyName, location.toByteArray(), true);
}
@Override
public ListenableFuture<Boolean> setStatefulCheckpoints(
CheckpointManager.StatefulConsistentCheckpoints checkpoint, String topologyName) {
return setData(StateLocation.STATEFUL_CHECKPOINT, topologyName, checkpoint.toByteArray(), true);
}
@Override
protected Lock getLock(String path) {
return new FileSystemLock(path);
}
@Override
public void close() {
// We would not clear anything here
// Scheduler kill interface should take care of the cleaning
}
public static void main(String[] args) throws ExecutionException, InterruptedException,
IllegalAccessException, ClassNotFoundException, InstantiationException {
Config config = Config.newBuilder()
.put(Key.STATEMGR_ROOT_PATH,
System.getProperty("user.home") + "/.herondata/repository/state/local")
.build();
LocalFileSystemStateManager stateManager = new LocalFileSystemStateManager();
stateManager.doMain(args, config);
}
}