/* * 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.operations.service.cluster; import org.apache.thrift.TException; import org.kaaproject.kaa.common.hash.EndpointObjectHash; import org.kaaproject.kaa.server.common.Base64Util; import org.kaaproject.kaa.server.common.thrift.KaaThriftService; import org.kaaproject.kaa.server.common.thrift.gen.operations.OperationsThriftService.Iface; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftActorClassifier; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftClusterEntityType; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftEndpointConfigurationRefreshMessage; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftEndpointDeregistrationMessage; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftEntityAddress; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftEntityClusterAddress; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftEntityRouteMessage; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftRouteOperation; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftServerProfileUpdateMessage; import org.kaaproject.kaa.server.common.thrift.gen.operations.ThriftUnicastNotificationMessage; import org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo; import org.kaaproject.kaa.server.common.zk.operations.OperationsNode; import org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener; import org.kaaproject.kaa.server.node.service.thrift.OperationsServiceMsg; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.ActorClassifier; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EndpointAddress; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EndpointClusterAddress; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EndpointRouteMessage; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.EntityClusterAddress; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.RouteOperation; import org.kaaproject.kaa.server.operations.service.akka.messages.core.route.ThriftEndpointActorMsg; import org.kaaproject.kaa.server.operations.service.config.OperationsServerConfig; import org.kaaproject.kaa.server.resolve.OperationsServerResolver; import org.kaaproject.kaa.server.thrift.NeighborConnection; import org.kaaproject.kaa.server.thrift.NeighborTemplate; import org.kaaproject.kaa.server.thrift.Neighbors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @Service public class DefaultClusterService implements ClusterService { /** * The Constant LOG. */ private static final Logger LOG = LoggerFactory.getLogger(DefaultClusterService.class); @Autowired private OperationsServerConfig operationsServerConfig; /** * ID is thriftHost:thriftPort. */ private volatile String id; private volatile Neighbors<MessageTemplate, OperationsServiceMsg> neighbors; private volatile OperationsNode operationsNode; private volatile OperationsServerResolver resolver; private ClusterServiceListener listener; public DefaultClusterService() { super(); } /** * Bean init-method. */ @PostConstruct public void initBean() { LOG.info("Init default cluster service."); neighbors = new Neighbors<>(KaaThriftService.OPERATIONS_SERVICE, new MessageTemplate(), operationsServerConfig.getMaxNumberNeighborConnections()); } /** * bean destroy method. */ @PreDestroy public void onStop() { if (neighbors != null) { LOG.info("Shutdown of control service neighbors started!"); neighbors.shutdown(); LOG.info("Shutdown of control service neighbors complete!"); } } @Override public String getNodeId() { return id; } @Override public void setZkNode(OperationsNode operationsNode) { this.operationsNode = operationsNode; this.id = Neighbors.getServerId(this.operationsNode.getNodeInfo().getConnectionInfo()); neighbors.setZkNode( KaaThriftService.OPERATIONS_SERVICE, this.operationsNode.getNodeInfo().getConnectionInfo(), operationsNode); if (resolver != null) { updateResolver(this.resolver); } } @Override public void setResolver(OperationsServerResolver resolver) { this.resolver = resolver; if (operationsNode != null) { updateResolver(this.resolver); } } private void updateResolver(final OperationsServerResolver resolver) { operationsNode.addListener(new OperationsNodeListener() { @Override public void onNodeUpdated(OperationsNodeInfo node) { if (LOG.isDebugEnabled()) { LOG.debug("Update of node {} is pushed to resolver {}", DefaultClusterService.toString(node), resolver); } resolver.onNodeUpdated(node); } @Override public void onNodeRemoved(OperationsNodeInfo node) { if (LOG.isDebugEnabled()) { LOG.debug("Remove of node {} is pushed to resolver {}", DefaultClusterService.toString(node), resolver); } resolver.onNodeRemoved(node); notifyListener(); } @Override public void onNodeAdded(OperationsNodeInfo node) { if (LOG.isDebugEnabled()) { LOG.debug("Add of node {} is pushed to resolver {}", DefaultClusterService.toString(node), resolver); } resolver.onNodeAdded(node); notifyListener(); } private void notifyListener() { if (listener != null) { listener.onClusterUpdated(); } } }); for (OperationsNodeInfo info : operationsNode.getCurrentOperationServerNodes()) { resolver.onNodeUpdated(info); } } private static String toString(OperationsNodeInfo node) { return "[" + node.getConnectionInfo().getThriftHost() + ":" + node.getConnectionInfo().getThriftPort() + ":" + node.getTimeStarted() + "]-[" + node.getLoadInfo() + "]"; } @Override public void setListener(ClusterServiceListener listener) { this.listener = listener; } @Override public boolean isMainEntityNode(EndpointObjectHash entityId) { return isMainEntityNode(entityId.getData()); } @Override public boolean isMainEntityNode(byte[] entityId) { String entityIdStr = Base64Util.encode(entityId); OperationsNodeInfo info = resolver.getNode(entityIdStr); if (info == null) { return false; } String nodeId = Neighbors.getServerId(info.getConnectionInfo()); LOG.trace("Comparing {} to {} for entity {}", id, nodeId, entityIdStr); return id.equals(nodeId); } @Override public String getEntityNode(EndpointObjectHash entityId) { return getEntityNode(entityId.getData()); } @Override public String getEntityNode(byte[] entityId) { OperationsNodeInfo info = resolver.getNode(Base64Util.encode(entityId)); if (info != null) { return Neighbors.getServerId(info.getConnectionInfo()); } return null; } @Override public String sendRouteMessage(EndpointRouteMessage msg) { String serverId = getEntityNode(msg.getAddress().getEndpointKey()); sendOperationsServiceMessage(serverId, OperationsServiceMsg.fromRoute(toThriftMsg(msg))); return serverId; } @Override public void sendUnicastNotificationMessage(String serverId, ThriftUnicastNotificationMessage msg) { sendOperationsServiceMessage(serverId, OperationsServiceMsg.fromNotification(msg)); } @Override public void sendServerProfileUpdateMessage(String serverId, ThriftServerProfileUpdateMessage msg) { sendOperationsServiceMessage(serverId, OperationsServiceMsg.fromServerProfileUpdateMessage(msg)); } @Override public void sendEndpointConfigurationRefreshMessage(String serverId, ThriftEndpointConfigurationRefreshMessage msg) { sendOperationsServiceMessage(serverId, OperationsServiceMsg.fromEndpointConfigurationRefresh(msg)); } @Override public void sendEndpointConfigurationRefreshMessage(ThriftEndpointConfigurationRefreshMessage msg) { EndpointAddress address = fromThriftAddress(msg.getAddress()); ActorClassifier classifier = fromThriftActorClassifier(msg.getActorClassifier()); listener.onEndpointActorMsg(new ThriftEndpointActorMsg<>(address, classifier, msg)); } private void sendOperationsServiceMessage(String serverId, OperationsServiceMsg msg) { NeighborConnection<MessageTemplate, OperationsServiceMsg> server = neighbors.getNeghborConnection(serverId); if (server == null) { LOG.warn("Specified server {} not found in neighbors list", serverId); } else { sendMessagesToServer(server, Collections.singleton(msg)); } } @Override public void onEntityRouteMessages(List<ThriftEntityRouteMessage> msgs) { for (ThriftEntityRouteMessage msg : msgs) { switch (msg.getAddress().getAddress().getEntityType()) { case ENDPOINT: listener.onRouteMsg(fromThriftMsg(msg)); break; default: LOG.error("Unknown entity type: {}", msg.getAddress().getAddress().getEntityType()); } } } @Override public void onUnicastNotificationMessage(ThriftUnicastNotificationMessage msg) { EndpointAddress address = fromThriftAddress(msg.getAddress()); ActorClassifier classifier = fromThriftActorClassifier(msg.getActorClassifier()); listener.onEndpointActorMsg(new ThriftEndpointActorMsg<>(address, classifier, msg)); } @Override public void onServerProfileUpdateMessage(ThriftServerProfileUpdateMessage msg) { EndpointAddress address = fromThriftAddress(msg.getAddress()); ActorClassifier classifier = fromThriftActorClassifier(msg.getActorClassifier()); listener.onEndpointActorMsg(new ThriftEndpointActorMsg<>(address, classifier, msg)); } @Override public void onEndpointDeregistrationMessage(ThriftEndpointDeregistrationMessage msg) { EndpointAddress address = fromThriftAddress(msg.getAddress()); ActorClassifier classifier = fromThriftActorClassifier(msg.getActorClassifier()); listener.onEndpointActorMsg(new ThriftEndpointActorMsg<>(address, classifier, msg)); } private EndpointAddress fromThriftAddress(ThriftEntityAddress source) { return new EndpointAddress( source.getTenantId(), source.getApplicationToken(), EndpointObjectHash.fromBytes(source.getEntityId())); } private EndpointClusterAddress fromThriftAddress(ThriftEntityClusterAddress source) { ThriftEntityAddress address = source.getAddress(); EndpointObjectHash endpointKey = EndpointObjectHash.fromBytes(address.getEntityId()); return new EndpointClusterAddress( source.getNodeId(), address.getTenantId(), address.getApplicationToken(), endpointKey); } private ActorClassifier fromThriftActorClassifier(ThriftActorClassifier actorClassifier) { return ActorClassifier.valueOf(actorClassifier.name()); } private EndpointRouteMessage fromThriftMsg(ThriftEntityRouteMessage source) { return new EndpointRouteMessage( fromThriftAddress(source.getAddress()), fromThriftOperation(source.getOperation())); } private RouteOperation fromThriftOperation(ThriftRouteOperation operation) { switch (operation) { case ADD: return RouteOperation.ADD; case UPDATE: return RouteOperation.UPDATE; case DELETE: return RouteOperation.DELETE; default: return RouteOperation.DELETE; } } private void sendMessagesToServer( NeighborConnection<MessageTemplate, OperationsServiceMsg> server, Collection<OperationsServiceMsg> messages) { try { LOG.trace("Sending to server {} messages: {}", server.getId(), messages); server.sendMessages(messages); } catch (InterruptedException ex) { LOG.error("Error sending events to server: ", ex); } } private ThriftEntityRouteMessage toThriftMsg(EndpointRouteMessage source) { ThriftEntityRouteMessage msg = new ThriftEntityRouteMessage(); msg.setAddress(toAddress(source.getAddress())); msg.setOperation(toOperation(source.getOperation())); return msg; } private ThriftRouteOperation toOperation(RouteOperation operation) { switch (operation) { case ADD: return ThriftRouteOperation.ADD; case UPDATE: return ThriftRouteOperation.UPDATE; case DELETE: return ThriftRouteOperation.DELETE; default: return ThriftRouteOperation.DELETE; } } private ThriftEntityClusterAddress toAddress(EndpointClusterAddress source) { ThriftEntityClusterAddress address = toEntityClusterAddress(source); address.getAddress().setEntityType(ThriftClusterEntityType.ENDPOINT); return address; } private ThriftEntityClusterAddress toEntityClusterAddress(EntityClusterAddress source) { ThriftEntityClusterAddress address = new ThriftEntityClusterAddress(); address.setNodeId(source.getNodeId()); address.setAddress(toEntityAddress(source)); return address; } private ThriftEntityAddress toEntityAddress(EntityClusterAddress source) { ThriftEntityAddress address = new ThriftEntityAddress(); address.setTenantId(source.getTenantId()); address.setApplicationToken(source.getAppToken()); address.setEntityId(source.getEntityId()); return address; } @Override public void shutdown() { LOG.info("Cluster Service shutdown()...."); neighbors.shutdown(); } private static class MessageTemplate implements NeighborTemplate<OperationsServiceMsg> { public MessageTemplate() { super(); } @Override public void process(Iface client, List<OperationsServiceMsg> messages) throws TException { OperationsServiceMsg.dispatch(client, messages); } @Override public void onServerError(String serverId, Exception ex) { LOG.warn("Failed to send data to [{}]", serverId); } } }