package org.fastcatsearch.cluster;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.fastcatsearch.control.JobService;
import org.fastcatsearch.control.ResultFuture;
import org.fastcatsearch.env.Environment;
import org.fastcatsearch.exception.FastcatSearchException;
import org.fastcatsearch.job.Job;
import org.fastcatsearch.service.AbstractService;
import org.fastcatsearch.service.ServiceManager;
import org.fastcatsearch.settings.NodeListSettings;
import org.fastcatsearch.settings.NodeListSettings.NodeSettings;
import org.fastcatsearch.settings.Settings;
import org.fastcatsearch.transport.TransportException;
import org.fastcatsearch.transport.TransportModule;
import org.fastcatsearch.transport.common.SendFileResultFuture;
import org.fastcatsearch.util.FileUtils;
public class NodeService extends AbstractService implements NodeLoadBalancable {
private static NodeLoadBalancer loadBalancer;
private TransportModule transportModule;
private Node myNode;
private Node masterNode;
private Map<String, Node> nodeMap;
public NodeService(Environment environment, Settings settings, ServiceManager serviceManager) {
super(environment, settings, serviceManager);
}
@Override
protected boolean doStart() throws FastcatSearchException {
JobService jobService = serviceManager.getService(JobService.class);
String myNodeId = environment.myNodeId();
String masterNodeId = environment.masterNodeId();
nodeMap = new HashMap<String, Node>();
NodeListSettings nodeListSettings = environment.settingManager().getNodeListSettings();
if(nodeListSettings != null){
for(NodeSettings nodeSetting : nodeListSettings.getNodeList()){
String id = nodeSetting.getId();
boolean isEnabled = nodeSetting.isEnabled();
Node node = new Node(nodeSetting);
nodeMap.put(id, node);
if (isEnabled) {
node.setEnabled();
} else {
node.setDisabled();
}
if(myNodeId.equals(id)){
myNode = node;
}
if (masterNodeId.equals(id)) {
masterNode = node;
}
}
}
if (myNode == null) {
throw new FastcatSearchException("ERR-00300");
}
if (masterNode == null) {
throw new FastcatSearchException("ERR-00301");
}
//자기자신은 active하다.
myNode.setActive();
int servicePort = environment.settingManager().getIdSettings().getInt("servicePort");
myNode.setServicePort(servicePort);
boolean hasSeparateDataNetwork = myNode.dataAddress() != null;
transportModule = new TransportModule(environment, settings.getSubSettings("transport"), myNode.port(), jobService, hasSeparateDataNetwork);
if (!transportModule.load()) {
throw new FastcatSearchException("ERR-00305");
}
NodeHandshakeJob nodeHandshakeJob = new NodeHandshakeJob(myNode.id(), servicePort);
for (Node node : nodeMap.values()) {
if (node!=null && node.isEnabled() && !node.equals(myNode)) {
try {
transportModule.connectToNode(node);
node.setActive();
transportModule.sendRequest(node, nodeHandshakeJob);
} catch (TransportException e) {
logger.error("Cannot connect to {}", node);
node.setInactive();
}
}
}
loadBalancer = new NodeLoadBalancer();
return true;
}
@Override
protected boolean doStop() throws FastcatSearchException {
return transportModule.unload();
}
@Override
protected boolean doClose() throws FastcatSearchException {
return true;
}
public Collection<Node> getNodeList() {
return nodeMap.values();
}
public List<Node> getNodeArrayList() {
return new ArrayList<Node>(nodeMap.values());
}
public Node getNodeById(String id) {
return nodeMap.get(id);
}
public List<Node> getNodeById(List<String> nodeIdList) {
if(nodeIdList == null){
return new ArrayList<Node>();
}
List<Node> result = new ArrayList<Node>(nodeIdList.size());
for (String nodeId : nodeIdList) {
result.add(nodeMap.get(nodeId));
}
return result;
}
public Node getMyNode() {
return myNode;
}
public Node getMasterNode() {
return masterNode;
}
public boolean isMaster() {
if (masterNode != null && myNode != null) {
return myNode.equals(masterNode);
}
return false;
}
public boolean isMyNode(Node node) {
return myNode.equals(node);
}
public ResultFuture sendRequestToMaster(final Job job) {
if (masterNode.equals(myNode)) {
return JobService.getInstance().offer(job);
}
try {
return transportModule.sendRequest(masterNode, job);
} catch (TransportException e) {
logger.error("sendRequest 에러 : {}", e.getMessage());
}
return null;
}
public ResultFuture sendRequest(final Node node, final Job job) {
if(node == null || job == null){
return null;
}
if (node.equals(myNode)) {
return JobService.getInstance().offer(job);
}
try {
return transportModule.sendRequest(node, job);
} catch (TransportException e) {
logger.error("sendRequest 에러 : {}", e.getMessage());
}
return null;
}
/*
* 파일만 전송가능. 디렉토리는 전송불가.
* 동일노드로는 전송불가.
*/
public SendFileResultFuture sendFile(final Node node, File sourcefile, File targetFile) throws TransportException {
if (sourcefile.isDirectory()) {
return null;
}
//노드가 같고, file도 같다면 전송하지 않는다.
if (node.equals(myNode)) {
File a = environment.filePaths().makePath(sourcefile.getPath()).file();
File b = environment.filePaths().makePath(targetFile.getPath()).file();
// logger.warn("compare. [{}] : [{}] = {}", a.getAbsolutePath(), b.getAbsolutePath(), a.getAbsolutePath().equals(b.getAbsolutePath()));
if(a.getAbsolutePath().equals(b.getAbsolutePath())){
logger.warn("Cannot send same file to same node. Skip! {}", sourcefile);
return null;
}else{
//다르다면 로컬 복사한다.
try {
targetFile = environment.filePaths().makePath(targetFile.getPath()).file();
logger.warn("Copy files locally. {} > {}", sourcefile.getAbsolutePath(), targetFile.getAbsolutePath());
FileUtils.copyFile(sourcefile, targetFile);
SendFileResultFuture f = new SendFileResultFuture(0, null);
f.put(null, true);
return f;
} catch (IOException e) {
throw new TransportException("Fail to copy local file. " + sourcefile + "> " + targetFile);
}
}
}
return transportModule.sendFile(node, sourcefile, targetFile);
}
@Override
public void updateLoadBalance(String collectionId, List<String> dataNodeIdList) {
List<Node> list = getNodeById(dataNodeIdList);
loadBalancer.update(collectionId, list);
}
@Override
public Node getBalancedNode(String collectionId){
Node node = loadBalancer.getBalancedNode(collectionId);
logger.debug("#Balanced node [{}] >> {}", collectionId, node);
return node;
}
/**
* FIXME:노드 정지 및 삭제에 대한 기능이 들어있지 않음.
* @param nodeListSettings
*/
public void updateNode(NodeListSettings nodeListSettings) {
List<NodeSettings> nodeSettingList = nodeListSettings.getNodeList();
logger.trace("updating node..");
// if(nodeSettingList.size() < nodeMap.size()) {
//삭제된 경우.
Iterator<Entry<String, Node>> iterator = nodeMap.entrySet().iterator();
while(iterator.hasNext()) {
Entry<String, Node> entry = iterator.next();
//못찾으면 삭제된 것이다.
if(nodeListSettings.findNodeById(entry.getKey())==-1) {
//노드 삭제
iterator.remove();
// nodeMap.remove(key);
}
}
// } else {
for(int inx=0;inx< nodeSettingList.size();inx++) {
NodeSettings setting = nodeSettingList.get(inx);
String nodeId = setting.getId();
String name = setting.getName();
boolean enabled = setting.isEnabled();
InetSocketAddress address = new InetSocketAddress(setting.getAddress(), setting.getPort());
Node node = null;
if(nodeMap.containsKey(nodeId)) {
node = nodeMap.get(nodeId);
//주소 및 포트가 변경 되었다면
logger.debug("check node {} ({}:{}) : ({}:{})", nodeId, address.getAddress(), address.getPort(), node.address().getAddress(), node.address().getPort());
Node newNode = new Node(setting);
nodeMap.put(nodeId, newNode);
if (!(address.getAddress().getHostAddress() != null
&& address.getAddress().getHostAddress().equals(node.address().getAddress().getHostAddress())
&& address.getPort() == node.address().getPort()
)) {
//기존 노드의 삭제 후 새 노드를 시작함.
logger.debug("updating node {} ({}/{}:{})..", inx, nodeId, node.address(), node.port());
}else {
if(node.isActive()) {
newNode.setActive();
}
}
} else {
//노드추가
node = new Node(setting);
logger.debug("add new node.. {}", node);
nodeMap.put(nodeId, node);
}
}
// }
environment.settingManager().storeNodeListSettings(nodeListSettings);
}
}