/*
* Copyright 2014-2016 CyberVision, Inc.
*
* 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.kaaproject.kaa.server.common.zk;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.UnhandledErrorListener;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.kaaproject.kaa.common.avro.AvroByteArrayConverter;
import org.kaaproject.kaa.server.common.zk.control.ControlNodeListener;
import org.kaaproject.kaa.server.common.zk.gen.BootstrapNodeInfo;
import org.kaaproject.kaa.server.common.zk.gen.ControlNodeInfo;
import org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The Class ControlNodeTracker.
*/
public abstract class ControlNodeTracker implements ControlNodeAware, Closeable {
/**
* The Constant CONTROL_SERVER_NODE_PATH.
*/
protected static final String CONTROL_SERVER_NODE_PATH = "/controlServerNode";
/**
* The Constant OPERATIONS_SERVER_NODE_PATH.
*/
protected static final String OPERATIONS_SERVER_NODE_PATH = "/operationsServerNodes";
/**
* The Constant BOOTSTRAP_SERVER_NODE_PATH.
*/
protected static final String BOOTSTRAP_SERVER_NODE_PATH = "/bootstrapServerNodes";
/**
* The Constant LOG.
*/
private static final Logger LOG = LoggerFactory
.getLogger(ControlNodeTracker.class);
/**
* The listeners.
*/
private final List<ControlNodeListener> listeners;
/**
* The client.
*/
protected CuratorFramework zkClient;
/**
* The node path.
*/
protected String nodePath;
/**
* The control node avro converter.
*/
protected ThreadLocal<AvroByteArrayConverter<ControlNodeInfo>> controlNodeAvroConverter =
new ThreadLocal<AvroByteArrayConverter<ControlNodeInfo>>() {
@Override
protected AvroByteArrayConverter<ControlNodeInfo> initialValue() {
return new AvroByteArrayConverter<ControlNodeInfo>(ControlNodeInfo.class);
}
};
/**
* The endpoint node avro converter.
*/
protected ThreadLocal<AvroByteArrayConverter<OperationsNodeInfo>> operationsNodeAvroConverter =
new ThreadLocal<AvroByteArrayConverter<OperationsNodeInfo>>() {
@Override
protected AvroByteArrayConverter<OperationsNodeInfo> initialValue() {
return new AvroByteArrayConverter<OperationsNodeInfo>(OperationsNodeInfo.class);
}
};
/**
* The bootstrap node avro converter.
*/
protected ThreadLocal<AvroByteArrayConverter<BootstrapNodeInfo>> bootstrapNodeAvroConverter =
new ThreadLocal<AvroByteArrayConverter<BootstrapNodeInfo>>() {
@Override
protected AvroByteArrayConverter<BootstrapNodeInfo> initialValue() {
return new AvroByteArrayConverter<BootstrapNodeInfo>(BootstrapNodeInfo.class);
}
};
/**
* The control cache.
*/
private NodeCache controlCache;
/**
* The errors listener.
*/
private final UnhandledErrorListener errorsListener = new UnhandledErrorListener() {
@Override
public void unhandledError(String message, Throwable ex) {
LOG.error("Unrecoverable error: " + message, ex);
try {
close();
} catch (IOException ioe) {
LOG.warn("Exception when closing.", ioe);
}
}
};
/**
* Instantiates a new control node tracker.
*/
public ControlNodeTracker() {
super();
this.listeners = new CopyOnWriteArrayList<ControlNodeListener>();
}
/**
* Start.
*
* @throws Exception the exception
*/
public void start() throws Exception { //NOSONAR
LOG.info("Starting node tracker");
zkClient.getUnhandledErrorListenable().addListener(errorsListener);
if (createZkNode()) {
controlCache = new NodeCache(zkClient, CONTROL_SERVER_NODE_PATH);
controlCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
ChildData currentData = controlCache.getCurrentData();
if (currentData == null) {
LOG.warn("Control service node died!");
onNoMaster();
} else {
LOG.warn("Control service node changed!");
onMasterChange(currentData);
}
}
});
controlCache.start();
} else {
LOG.warn("Failed to create ZK node!");
}
}
public abstract boolean createZkNode() throws IOException;
/**
* On no master.
*
* @throws IOException Signals that an I/O exception has occurred.
*/
protected void onNoMaster() throws IOException {
for (ControlNodeListener listener : listeners) {
listener.onControlNodeDown();
}
}
/**
* On master change.
*
* @param currentData the current data
*/
protected void onMasterChange(ChildData currentData) {
ControlNodeInfo controlServerInfo = extractControlServerInfo(currentData);
for (ControlNodeListener listener : listeners) {
listener.onControlNodeChange(controlServerInfo);
}
}
/**
* Checks if is connected.
*
* @return true, if is connected
*/
public boolean isConnected() {
return zkClient.getZookeeperClient().isConnected();
}
/**
* Adds the listener.
*
* @param listener the listener
*/
public void addListener(ControlNodeListener listener) {
LOG.debug("Listener registered: " + listener);
listeners.add(listener);
}
/**
* Removes the listener.
*
* @param listener the listener
* @return true, if successful
*/
public boolean removeListener(ControlNodeListener listener) {
if (listeners.remove(listener)) {
LOG.debug("Listener removed: " + listener);
return true;
} else {
LOG.debug("Listener not found: " + listener);
return false;
}
}
/*
* (non-Javadoc)
*
* @see java.io.Closeable#close()
*/
@Override
public void close() throws IOException {
LOG.info("Closing");
listeners.clear();
if (controlCache != null) {
controlCache.close();
}
if (nodePath != null) {
try {
zkClient.delete().forPath(nodePath);
LOG.debug("Node with path {} successfully deleted", nodePath);
} catch (Exception ex) {
LOG.debug("Failed to delete node", ex);
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.kaaproject.kaa.server.common.zk.ControlNodeAware#getControlServerInfo
* ()
*/
@Override
public ControlNodeInfo getControlServerInfo() {
if (controlCache != null && controlCache.getCurrentData() != null) {
return extractControlServerInfo(controlCache.getCurrentData());
} else {
return null;
}
}
/**
* Extract control service info.
*
* @param currentData the current data
* @return the control node info
*/
private ControlNodeInfo extractControlServerInfo(ChildData currentData) {
ControlNodeInfo controlServerInfo = null;
try {
controlServerInfo = controlNodeAvroConverter.get().fromByteArray(currentData.getData(),
controlServerInfo);
} catch (IOException ex) {
LOG.error("error reading control service info", ex);
}
return controlServerInfo;
}
public boolean doZkClientAction(ZkClientAction action) throws IOException {
return doZkClientAction(action, false);
}
/**
* Do Zookeeper client action.
*
* @param action the Zookeeper client action
* @param throwIoException define throw or not IOException
* @return boolean 'true' if doWithZkClient method works without exceptions
* @throws IOException the IOException
*/
public boolean doZkClientAction(ZkClientAction action, boolean throwIoException)
throws IOException {
try {
action.doWithZkClient(zkClient);
return true;
} catch (Exception ex) {
LOG.error("Unknown Error", ex);
close();
if (throwIoException) {
throw new IOException(ex);
} else {
return false;
}
}
}
public static interface ZkClientAction {
void doWithZkClient(CuratorFramework client) throws Exception; //NOSONAR
}
}