/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.jenkins.results.parser; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author Peter Yoo */ public class LoadBalancerUtil { public static String getMostAvailableMasterURL(Properties properties) throws Exception { long start = System.currentTimeMillis(); int retries = 0; while (true) { try { String baseInvocationURL = properties.getProperty( "base.invocation.url"); String masterPrefix = _getMasterPrefix(baseInvocationURL); if (masterPrefix.equals(baseInvocationURL)) { return baseInvocationURL; } List<JenkinsMaster> jenkinsMasters = _getJenkinsMasters( masterPrefix, properties); long nextUpdateTimestamp = _getNextUpdateTimestamp( masterPrefix); if (nextUpdateTimestamp < System.currentTimeMillis()) { _updateJenkinsMasters(jenkinsMasters); _setNextUpdateTimestamp( masterPrefix, System.currentTimeMillis() + _updateInterval); } Collections.sort(jenkinsMasters); JenkinsMaster mostAvailableJenkinsMaster = jenkinsMasters.get( 0); StringBuilder sb = new StringBuilder(); for (JenkinsMaster jenkinsMaster : jenkinsMasters) { sb.append(jenkinsMaster.getMasterName()); sb.append(" : "); sb.append(jenkinsMaster.getAvailableSlavesCount()); sb.append("\n"); } System.out.print(sb); sb = new StringBuilder(); sb.append("\nMost available master "); sb.append(mostAvailableJenkinsMaster.getMasterName()); sb.append(" has "); sb.append(mostAvailableJenkinsMaster.getAvailableSlavesCount()); sb.append(" available slaves."); System.out.println(sb); int invokedBatchSize = 0; try { invokedBatchSize = Integer.parseInt( properties.getProperty("invoked.job.batch.size")); } catch (Exception e) { invokedBatchSize = 1; } mostAvailableJenkinsMaster.addRecentBatch(invokedBatchSize); return "http://" + mostAvailableJenkinsMaster.getMasterName(); } catch (Exception e) { if (retries < _MAX_RETRIES) { retries++; continue; } throw e; } finally { System.out.println( "Got most available master URL in " + JenkinsResultsParserUtil.toDurationString( System.currentTimeMillis() - start)); } } } public static String getMostAvailableMasterURL( String... overridePropertiesArray) throws Exception { return getMostAvailableMasterURL(null, overridePropertiesArray); } public static String getMostAvailableMasterURL( String propertiesURL, String[] overridePropertiesArray) throws Exception { Properties properties = new Properties(); if (propertiesURL == null) { properties = JenkinsResultsParserUtil.getBuildProperties(); } else { properties = new Properties(); String propertiesString = JenkinsResultsParserUtil.toString( JenkinsResultsParserUtil.getLocalURL(propertiesURL), false); properties.load(new StringReader(propertiesString)); } if ((overridePropertiesArray != null) && (overridePropertiesArray.length > 0) && ((overridePropertiesArray.length % 2) == 0)) { for (int i = 0; i < overridePropertiesArray.length; i += 2) { String overridePropertyName = overridePropertiesArray[i]; String overridePropertyValue = overridePropertiesArray[i + 1]; if (overridePropertyValue == null) { continue; } properties.setProperty( overridePropertyName, overridePropertyValue); } } return getMostAvailableMasterURL(properties); } public static void setUpdateInterval(long interval) { _updateInterval = interval; } private static List<String> _getBlacklist(Properties properties) { String blacklistString = properties.getProperty( "jenkins.load.balancer.blacklist", ""); System.out.println("Blacklist: " + blacklistString); if (blacklistString.isEmpty()) { return Collections.emptyList(); } List<String> blacklist = new ArrayList<>(); for (String blacklistItem : blacklistString.split(",")) { blacklist.add(blacklistItem.trim()); } return blacklist; } private static List<JenkinsMaster> _getJenkinsMasters( String masterPrefix, Properties properties) { List<JenkinsMaster> allJenkinsMasters = null; if (!_jenkinsMasters.containsKey(masterPrefix)) { allJenkinsMasters = new ArrayList<>(); for (String masterName : JenkinsResultsParserUtil.getMasters( properties, masterPrefix)) { JenkinsMaster jenkinsMaster = new JenkinsMaster( masterName, properties.getProperty( JenkinsResultsParserUtil.combine( "jenkins.local.url[", masterName, "]"))); allJenkinsMasters.add(jenkinsMaster); } _jenkinsMasters.put(masterPrefix, allJenkinsMasters); } else { allJenkinsMasters = _jenkinsMasters.get(masterPrefix); } List<String> blacklist = _getBlacklist(properties); if (blacklist.isEmpty()) { return allJenkinsMasters; } List<JenkinsMaster> filteredJenkinsMasters = new ArrayList<>( allJenkinsMasters.size()); for (JenkinsMaster jenkinsMaster : allJenkinsMasters) { if (blacklist.contains(jenkinsMaster.getMasterName())) { continue; } filteredJenkinsMasters.add(jenkinsMaster); } return filteredJenkinsMasters; } private static String _getMasterPrefix(String baseInvocationURL) { Matcher matcher = _urlPattern.matcher(baseInvocationURL); if (!matcher.find()) { return baseInvocationURL; } return matcher.group("masterPrefix"); } private static long _getNextUpdateTimestamp(String masterPrefix) { if (!_nextUpdateTimestampMap.containsKey(masterPrefix)) { return 0; } else return _nextUpdateTimestampMap.get(masterPrefix); } private static void _setNextUpdateTimestamp( String masterPrefix, long nextUpdateTimestamp) { _nextUpdateTimestampMap.put(masterPrefix, nextUpdateTimestamp); } private static void _updateJenkinsMasters( List<JenkinsMaster> jenkinsMasters) { ExecutorService executorService = Executors.newFixedThreadPool( jenkinsMasters.size()); for (final JenkinsMaster jenkinsMaster : jenkinsMasters) { executorService.execute( new Runnable() { @Override public void run() { jenkinsMaster.update(); } }); } executorService.shutdown(); try { executorService.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException ie) { throw new RuntimeException(ie); } List<JenkinsMaster> unavailableJenkinsMasters = new ArrayList<>( jenkinsMasters.size()); for (JenkinsMaster jenkinsMaster : jenkinsMasters) { if (!jenkinsMaster.isAvailable()) { unavailableJenkinsMasters.add(jenkinsMaster); } } jenkinsMasters.removeAll(unavailableJenkinsMasters); if (jenkinsMasters.isEmpty()) { throw new RuntimeException( "Unable to communicate with any Jenkins masters"); } } private static final int _MAX_RETRIES = 3; private static final Map<String, List<JenkinsMaster>> _jenkinsMasters = new HashMap<>(); private static final Map<String, Long> _nextUpdateTimestampMap = new HashMap<>(); private static long _updateInterval = 1000 * 10; private static final Pattern _urlPattern = Pattern.compile( "http://(?<masterPrefix>.+-\\d?).liferay.com"); }