/* * 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.control.service.loadmgmt; import static java.text.MessageFormat.format; import static org.kaaproject.kaa.server.common.zk.ServerNameUtil.getNameFromConnectionInfo; import org.apache.thrift.TException; import org.kaaproject.kaa.server.common.thrift.KaaThriftService; import org.kaaproject.kaa.server.common.thrift.gen.bootstrap.BootstrapThriftService; import org.kaaproject.kaa.server.common.thrift.gen.bootstrap.BootstrapThriftService.Client; import org.kaaproject.kaa.server.common.thrift.gen.bootstrap.ThriftOperationsServer; import org.kaaproject.kaa.server.common.thrift.gen.operations.OperationsThriftService; import org.kaaproject.kaa.server.common.thrift.gen.operations.RedirectionRule; import org.kaaproject.kaa.server.common.thrift.util.ThriftActivity; import org.kaaproject.kaa.server.common.thrift.util.ThriftClient; import org.kaaproject.kaa.server.common.thrift.util.ThriftExecutor; import org.kaaproject.kaa.server.common.zk.ServerNameUtil; import org.kaaproject.kaa.server.common.zk.bootstrap.BootstrapNodeListener; import org.kaaproject.kaa.server.common.zk.control.ControlNode; import org.kaaproject.kaa.server.common.zk.gen.BootstrapNodeInfo; import org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo; import org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener; import org.kaaproject.kaa.server.control.service.loadmgmt.dynamicmgmt.OperationsServerLoadHistory; import org.kaaproject.kaa.server.control.service.loadmgmt.dynamicmgmt.Rebalancer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * DynamicLoadManager Class. * * @author Andrey Panasenko */ public class DynamicLoadManager implements OperationsNodeListener, BootstrapNodeListener { private static final int DEFAULT_PRIORITY = 10; private static final Logger LOG = LoggerFactory.getLogger(DynamicLoadManager.class); /** * Map to store Operations servers, key - DNS name host:port. */ private final Map<Integer, OperationsServerMeta> opsServersMap; /** * Map to store bootstrap services, key - DNS name host:port. */ private final Map<String, BootstrapNodeInfo> bootstrapsMap; private LoadDistributionService loadDistributionService; /** * The last bootstrap services update failed. */ private boolean lastBootstrapServersUpdateFailed = false; /** * Time to live of Operations server load history, in ms. */ private long opsLoadHistoryTtl = 600000; /** * The Constructor. * * @param loadDistributionService the load distribution service */ public DynamicLoadManager(LoadDistributionService loadDistributionService) { setLoadDistributionService(loadDistributionService); opsServersMap = new ConcurrentHashMap<Integer, OperationsServerMeta>(); bootstrapsMap = new ConcurrentHashMap<String, BootstrapNodeInfo>(); // Translate seconds to ms opsLoadHistoryTtl = loadDistributionService.getOpsServerHistoryTtl() * 1000; } /** * Run recalculate process for Operations server Load optimization. */ public void recalculate() { LOG.info("DynamicLoadManager recalculate() started... lastBootstrapServersUpdateFailed {}", lastBootstrapServersUpdateFailed); if (lastBootstrapServersUpdateFailed) { LOG.trace("Registred {} Bootstrap servers", bootstrapsMap.size()); lastBootstrapServersUpdateFailed = false; for (BootstrapNodeInfo bootstrapNodeInfo : bootstrapsMap.values()) { updateBootstrap(bootstrapNodeInfo); } } if (loadDistributionService.getRebalancer() != null) { Map<Integer, OperationsServerLoadHistory> opsServerHistory = new HashMap<>(); for (Integer accessPointId : opsServersMap.keySet()) { opsServerHistory.put(accessPointId, opsServersMap.get(accessPointId).history); } Map<Integer, List<RedirectionRule>> rules = loadDistributionService .getRebalancer() .recalculate(opsServerHistory); LOG.trace("DynamicLoadManager recalculate() got {} redirection rules", rules.size()); for (Integer accessPointId : rules.keySet()) { if (opsServersMap.containsKey(accessPointId)) { sendRedirectionRule(accessPointId, opsServersMap.get(accessPointId).nodeInfo, rules.get(accessPointId)); } else { LOG.error("Operations server {} redirection rule exist, " + "but NO server available, skip setting rule.", accessPointId); } } } } /** * Register listeners for Operations server nodes updates and Bootstrap * nodes updates. */ public void registerListeners() { LOG.trace("DynamicLoadManager register listeners..."); ControlNode pm = getLoadDistributionService().getZkService().getControlZkNode(); pm.addListener((OperationsNodeListener) this); pm.addListener((BootstrapNodeListener) this); } /** * Deregister listeners for Operations server nodes updates and Bootstrap * nodes updates. */ public void deregisterListeners() { LOG.trace("DynamicLoadManager deregister listeners..."); ControlNode pm = getLoadDistributionService().getZkService().getControlZkNode(); pm.removeListener((OperationsNodeListener) this); pm.removeListener((BootstrapNodeListener) this); } /* * (non-Javadoc) * * @see org.kaaproject.kaa.server.common.zk.bootstrap.BootstrapNodeListener# * onNodeAdded(org.kaaproject.kaa.server.common.zk.gen.BootstrapNodeInfo) */ @Override public void onNodeAdded(BootstrapNodeInfo nodeInfo) { final String dnsName = getNameFromConnectionInfo(nodeInfo.getConnectionInfo()); LOG.info("Bootstrap server {} added", dnsName); bootstrapsMap.put(dnsName, nodeInfo); updateBootstrap(nodeInfo); } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener * #onNodeAdded(org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo) */ @Override public void onNodeAdded(OperationsNodeInfo nodeInfo) { String dnsName = getNameFromConnectionInfo(nodeInfo.getConnectionInfo()); int accessPointId = ServerNameUtil.crc32(nodeInfo.getConnectionInfo()); addNewOperationsServer(accessPointId, dnsName, nodeInfo); LOG.info("Operations server [{}][{}] added. Updating {} Bootstrap servers", accessPointId, dnsName, bootstrapsMap.size()); for (BootstrapNodeInfo bootstrapNodeInfo : bootstrapsMap.values()) { updateBootstrap(bootstrapNodeInfo); } } /* * (non-Javadoc) * * @see org.kaaproject.kaa.server.common.zk.bootstrap.BootstrapNodeListener# * onNodeUpdated(org.kaaproject.kaa.server.common.zk.gen.BootstrapNodeInfo) */ @Override public void onNodeUpdated(BootstrapNodeInfo nodeInfo) { String dnsName = getNameFromConnectionInfo(nodeInfo.getConnectionInfo()); LOG.info("Bootstrap server {} updated", dnsName); bootstrapsMap.put(dnsName, nodeInfo); } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener * #onNodeUpdated * (org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo) */ @Override public void onNodeUpdated(OperationsNodeInfo nodeInfo) { String dnsName = getNameFromConnectionInfo(nodeInfo.getConnectionInfo()); int accessPointId = ServerNameUtil.crc32(nodeInfo.getConnectionInfo()); LOG.info("Operations server [{}][{}] updated", accessPointId, dnsName); if (opsServersMap.containsKey(accessPointId)) { opsServersMap.get(accessPointId).history.addOpsServerLoad(nodeInfo.getLoadInfo()); } else { addNewOperationsServer(accessPointId, dnsName, nodeInfo); } } /* * (non-Javadoc) * * @see org.kaaproject.kaa.server.common.zk.bootstrap.BootstrapNodeListener# * onNodeRemoved(org.kaaproject.kaa.server.common.zk.gen.BootstrapNodeInfo) */ @Override public void onNodeRemoved(BootstrapNodeInfo nodeInfo) { String dnsName = getNameFromConnectionInfo(nodeInfo.getConnectionInfo()); LOG.info("Bootstrap server {} removed", dnsName); bootstrapsMap.remove(dnsName); } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener * #onNodeRemoved * (org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo) */ @Override public void onNodeRemoved(OperationsNodeInfo nodeInfo) { String dnsName = getNameFromConnectionInfo(nodeInfo.getConnectionInfo()); int accessPointId = ServerNameUtil.crc32(nodeInfo.getConnectionInfo()); opsServersMap.remove(accessPointId); LOG.info("Operations server [{}][{}] removed. Updating {} Bootstrap servers", accessPointId, dnsName, bootstrapsMap.size()); for (BootstrapNodeInfo bootstrapNodeInfo : bootstrapsMap.values()) { updateBootstrap(bootstrapNodeInfo); } } private void addNewOperationsServer(int accessPointId, String dnsName, OperationsNodeInfo nodeInfo) { OperationsServerMeta meta = new OperationsServerMeta(null, nodeInfo); meta.opsServer = new ThriftOperationsServer(dnsName, DEFAULT_PRIORITY); opsServersMap.put(accessPointId, meta); } /** * Gets the load distribution service. * * @return the loadDistributionService */ public LoadDistributionService getLoadDistributionService() { return loadDistributionService; } /** * Sets the load distribution service. * * @param loadDistributionService the loadDistributionService to set */ public void setLoadDistributionService(LoadDistributionService loadDistributionService) { this.loadDistributionService = loadDistributionService; } /** * Update bootstrap. * * @param nodeInfo the node info */ private void updateBootstrap(BootstrapNodeInfo nodeInfo) { final String dnsName = getNameFromConnectionInfo(nodeInfo.getConnectionInfo()); LOG.debug("Update bootstrap service: {}", dnsName); try { ThriftClient<BootstrapThriftService.Client> thriftClient = new ThriftClient<>( nodeInfo.getConnectionInfo().getThriftHost().toString(), nodeInfo.getConnectionInfo().getThriftPort(), KaaThriftService.BOOTSTRAP_SERVICE, BootstrapThriftService.Client.class ); thriftClient.setThriftActivity(new ThriftActivity<BootstrapThriftService.Client>() { @Override public void isSuccess(boolean activitySuccess) { lastBootstrapServersUpdateFailed = !activitySuccess; LOG.info("Bootstrap {}: Operations services list updated {}", dnsName, activitySuccess ? "successfully" : "unsuccessfully"); } @Override public void doInTemplate(Client template) { try { // NOSONAR List<ThriftOperationsServer> operationsServersList = new ArrayList<>(); for (Integer accessPointId : opsServersMap.keySet()) { operationsServersList.add(opsServersMap.get(accessPointId).opsServer); LOG.trace("Bootstrap {} server: {}", dnsName, opsServersMap.get(accessPointId).opsServer.toString()); } LOG.trace("Bootstrap {} Operations servers list size {} ready to updates", dnsName, operationsServersList.size()); template.onOperationsServerListUpdate(operationsServersList); LOG.info("Bootstrap {} Operations servers list updated.", dnsName); } catch (TException ex) { lastBootstrapServersUpdateFailed = true; LOG.error(format("Bootstrap Operations servers list updated failed", dnsName), ex); } } }); ThriftExecutor.execute(thriftClient); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { lastBootstrapServersUpdateFailed = true; LOG.error(format("Bootstrap {0} Operations services list execute updated failed", dnsName), ex); } } /** * Send redirection rule. * * @param accessPointId the access point identifier * @param nodeInfo the node info * @param rules the list of redirection rules */ private void sendRedirectionRule(final Integer accessPointId, OperationsNodeInfo nodeInfo, final List<RedirectionRule> rules) { LOG.trace("Set redirection rule for Operations server: {}; Thrift: {}:{}", accessPointId, nodeInfo.getConnectionInfo().getThriftHost().toString(), nodeInfo.getConnectionInfo().getThriftPort()); try { ThriftClient<OperationsThriftService.Client> thriftClient = new ThriftClient<>( nodeInfo.getConnectionInfo().getThriftHost().toString(), nodeInfo.getConnectionInfo().getThriftPort(), KaaThriftService.OPERATIONS_SERVICE, OperationsThriftService.Client.class ); thriftClient.setThriftActivity(new ThriftActivity<OperationsThriftService.Client>() { @Override public void isSuccess(boolean activitySuccess) { LOG.info("Operations server {} redirection rule set {}", accessPointId, activitySuccess ? "successfully" : "unsuccessfully"); } @Override public void doInTemplate(OperationsThriftService.Client template) { try { // NOSONAR for (RedirectionRule rule : rules) { template.setRedirectionRule(rule); LOG.info("Operations {} set redirection rule: {} <> {}, {}", accessPointId, rule.getAccessPointId(), rule.getInitRedirectProbability(), rule.getSessionRedirectProbability()); } } catch (TException ex) { LOG.error(format("Operations server {0} set redirection rule failed", accessPointId), ex); } } }); ThriftExecutor.execute(thriftClient); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { LOG.error(format("Operations server {0} set redirection rule failed", accessPointId), ex); } } /** * Gets the operations Server history TTL. * * @return the opsLoadHistoryTtl */ public long getOpsServerHistoryTtl() { return opsLoadHistoryTtl; } /** * Sets the operations history TTL. * * @param opsServerHistoryTtl the opsLoadHistoryTtl to set */ public void setOpsServerHistoryTtl(long opsServerHistoryTtl) { this.opsLoadHistoryTtl = opsServerHistoryTtl; } /** * Dynamic rebalancer getter. * * @return Rebalancer instance. */ public Rebalancer getDynamicRebalancer() { return loadDistributionService.getRebalancer(); } class OperationsServerMeta { public ThriftOperationsServer opsServer; public OperationsServerLoadHistory history; public OperationsNodeInfo nodeInfo; /** * The Constructor. * * @param opsServer the Operations Server * @param nodeInfo the node info */ public OperationsServerMeta(ThriftOperationsServer opsServer, OperationsNodeInfo nodeInfo) { this.opsServer = opsServer; this.nodeInfo = nodeInfo; history = new OperationsServerLoadHistory(opsLoadHistoryTtl); history.addOpsServerLoad(nodeInfo.getLoadInfo()); } } }