/*
* Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved
* http://www.griddynamics.com
*
* This library is free software; you can redistribute it and/or modify it under the terms of
* the Apache License; either
* version 2.0 of the License, or any later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.griddynamics.jagger;
import com.griddynamics.jagger.coordinator.Coordinator;
import com.griddynamics.jagger.engine.e1.scenario.WorkloadTaskDistributor;
import com.griddynamics.jagger.exception.TechnicalException;
import com.griddynamics.jagger.jaas.storage.model.TestExecutionEntity;
import com.griddynamics.jagger.kernel.Kernel;
import com.griddynamics.jagger.launch.LaunchManager;
import com.griddynamics.jagger.launch.LaunchTask;
import com.griddynamics.jagger.launch.Launches;
import com.griddynamics.jagger.master.JaasEnvApiClient;
import com.griddynamics.jagger.master.JaasExecApiClient;
import com.griddynamics.jagger.master.Master;
import com.griddynamics.jagger.master.TerminateException;
import com.griddynamics.jagger.reporting.ReportingService;
import com.griddynamics.jagger.storage.StorageServerLauncher;
import com.griddynamics.jagger.storage.rdb.H2DatabaseServer;
import com.griddynamics.jagger.util.JaggerUrlClassLoader;
import com.griddynamics.jagger.util.JaggerXmlApplicationContext;
import com.griddynamics.jagger.util.generators.ConfigurationGenerator;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.eclipse.jetty.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
public final class JaggerLauncher {
public static final String ROLES = "chassis.roles";
public static final String MASTER_CONFIGURATION = "chassis.master.configuration";
public static final String REPORTER_CONFIGURATION = "chassis.reporter.configuration";
public static final String KERNEL_CONFIGURATION = "chassis.kernel.configuration";
public static final String COORDINATION_CONFIGURATION = "chassis.coordination.configuration";
public static final String COORDINATION_HTTP_CONFIGURATION = "chassis.coordination.http.configuration";
public static final String RDB_CONFIGURATION = "chassis.rdb.configuration";
public static final String INCLUDE_SUFFIX = ".include";
public static final String EXCLUDE_SUFFIX = ".exclude";
public static final String LOAD_SCENARIO_ID_PROP = "jagger.load.scenario.id.to.execute";
public static final String DEFAULT_ENVIRONMENT_PROPERTIES = "jagger.default.environment.properties";
public static final String USER_ENVIRONMENT_PROPERTIES = "jagger.user.environment.properties";
public static final String ENVIRONMENT_PROPERTIES = "jagger.environment.properties";
public static final String USER_CONFIGS_PACKAGE = "chassis.master.package.to.scan";
public static final String LOAD_SCENARIO_CLASSES_URL ="realtime.load.scenario.classes.url";
private static final Logger log = LoggerFactory.getLogger(JaggerLauncher.class);
private static final String DEFAULT_ENVIRONMENT_PROPERTIES_LOCATION =
"./configuration/basic/default.environment.properties";
private static final String DEFAULT_USER_ENVIRONMENT_PROPERTIES_LOCATION =
"./configuration/basic/default.user.properties";
private static final Properties environmentProperties = new Properties();
private static final Launches.LaunchManagerBuilder builder = Launches.builder();
public static void main(String[] args) throws Exception {
Thread memoryMonitorThread = new Thread("memory-monitor") {
@Override
public void run() {
for (; ; ) {
try {
log.info(
"Memory info: totalMemory={}, freeMemory={}", Runtime.getRuntime().totalMemory(),
Runtime.getRuntime().freeMemory()
);
Thread.sleep(60000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
memoryMonitorThread.setDaemon(true);
memoryMonitorThread.start();
String pid = ManagementFactory.getRuntimeMXBean().getName();
System.out.println(String.format("PID:%s", pid));
Properties props = System.getProperties();
for (Map.Entry<Object, Object> prop : props.entrySet()) {
log.info("{}: '{}'", prop.getKey(), prop.getValue());
}
log.info("");
URL directory = new URL("file:" + System.getProperty("user.dir") + "/");
loadBootProperties(directory, args[0], environmentProperties);
log.debug("Bootstrap properties:");
for (String propName : environmentProperties.stringPropertyNames()) {
log.debug(" {}={}", propName, environmentProperties.getProperty(propName));
}
String[] roles = environmentProperties.getProperty(ROLES).split(",");
Set<String> rolesSet = Sets.newHashSet(roles);
if (rolesSet.contains(Role.COORDINATION_SERVER.toString())) {
launchCoordinationServer(directory);
}
if (rolesSet.contains(Role.HTTP_COORDINATION_SERVER.toString())) {
launchCometdCoordinationServer(directory);
}
if (rolesSet.contains(Role.RDB_SERVER.toString())) {
launchRdbServer(directory);
}
if (rolesSet.contains(Role.MASTER.toString())) {
launchMaster(directory);
}
if (rolesSet.contains(Role.KERNEL.toString())) {
launchKernel(directory);
}
if (rolesSet.contains(Role.REPORTER.toString())) {
launchReporter(directory);
}
LaunchManager launchManager = builder.build();
int result = launchManager.launch();
System.exit(result);
}
private static void initCoordinator(ApplicationContext applicationContext) {
final Coordinator coordinator = (Coordinator) applicationContext.getBean("coordinator");
coordinator.waitForReady();
coordinator.initialize();
}
private static void launchMaster(final URL directory) {
// pass this property value for Spring's context:component-scan
System.setProperty(USER_CONFIGS_PACKAGE, environmentProperties.getProperty(USER_CONFIGS_PACKAGE));
LaunchTask masterTask = new LaunchTask() {
@Override
public void run() {
boolean isStandByMode = Boolean.parseBoolean(
environmentProperties.getProperty("realtime.enable.standby.mode", "false"));
if (isStandByMode) {
log.info("Starting Master in stand by mode...");
try (JaasEnvApiClient jaasEnvApiClient = new JaasEnvApiClient(
environmentProperties.getProperty("realtime.environment.id"),
environmentProperties.getProperty("realtime.jaas.endpoint"), Integer.parseInt(
environmentProperties.getProperty("realtime.status.report.interval.seconds")),
getAvailableConfigurations(directory)
)) {
jaasEnvApiClient.register();
while (jaasEnvApiClient.isStandBy()) {
doLoadScenarioExecution(jaasEnvApiClient.awaitNextExecution(), directory);
}
} catch (TerminateException | InterruptedException e) {
log.error("Master has been terminated.");
}
} else {
log.info("Starting Master in right away mode...");
doLaunchMaster(directory);
}
}
};
builder.addMainTask(masterTask);
}
private static void doLoadScenarioExecution(final JaasEnvApiClient.JaasResponse jaasResponse, final URL directory) {
JaasExecApiClient execApiClient = null;
try {
execApiClient = new JaasExecApiClient(jaasResponse.getExecutionId(),
environmentProperties.getProperty("realtime.jaas.endpoint")
);
Optional<TestExecutionEntity> optionalExecution = execApiClient.getExecution();
if (!optionalExecution.isPresent()) { // didn't manage to acquire an entity
return;
}
TestExecutionEntity execution = optionalExecution.get();
if (execution.getStatus() != TestExecutionEntity.TestExecutionStatus.PENDING) {
log.warn("Received execution with id '{}' is not in PENDING state. Going to proceed with next...",
execution.getId());
return;
}
execApiClient.startExecution();
String sessionId = null;
if (execution.getTestProjectURL() == null) { // then execute existing load scenario
environmentProperties.setProperty(LOAD_SCENARIO_ID_PROP, execution.getLoadScenarioId());
System.setProperty(USER_CONFIGS_PACKAGE, environmentProperties.getProperty(USER_CONFIGS_PACKAGE));
sessionId = doLaunchMaster(directory);
} else { // then execute a load scenario from provided artifact
sessionId = executeDynamicLoadScenario(execution.getLoadScenarioId(),
execution.getTestProjectURL(),
directory);
}
execApiClient.completeExecution(sessionId);
} catch (Exception e) {
log.error("Error during a load scenario execution", e);
if (execApiClient != null) {
execApiClient.failExecution(ExceptionUtils.getMessage(e) + " With root cause message: " + ExceptionUtils
.getRootCauseMessage(e));
}
}
}
private static String executeDynamicLoadScenario(String loadScenarioId, String customClassesUrl, URL directory)
throws IOException {
environmentProperties.setProperty(LOAD_SCENARIO_ID_PROP, loadScenarioId);
try {
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
final URL customClasses = new URL(customClassesUrl);
try (URLClassLoader urlClassLoader = new JaggerUrlClassLoader(new URL[]{customClasses})) {
Thread.currentThread().setContextClassLoader(urlClassLoader);
Properties userProps = new Properties();
String userPropsPath = "profiles/basic/environment.properties";
URL userPropsUrl = urlClassLoader.findResource(userPropsPath);
if (userPropsUrl == null) {
throw new IOException(String.format(
"Provided artifact %s is not properly formatted. There is no %s file inside.",
customClassesUrl, userPropsPath
));
}
userProps.load(userPropsUrl.openStream());
// pass this property value for Spring's context:component-scan
System.setProperty(USER_CONFIGS_PACKAGE, userProps.getProperty(USER_CONFIGS_PACKAGE));
if (StringUtils.isEmpty(loadScenarioId)) {
environmentProperties.setProperty(LOAD_SCENARIO_ID_PROP, userProps.getProperty(LOAD_SCENARIO_ID_PROP));
}
environmentProperties.setProperty(LOAD_SCENARIO_CLASSES_URL, customClassesUrl);
return doLaunchMaster(directory, urlClassLoader);
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
environmentProperties.remove(LOAD_SCENARIO_CLASSES_URL);
}
} catch (IOException e) {
log.error("I/O Error during load scenario execution launching.", e);
throw e;
}
}
private static Set<String> getAvailableConfigurations(final URL directory) {
AbstractXmlApplicationContext context = loadContext(directory, MASTER_CONFIGURATION, environmentProperties);
ConfigurationGenerator configurationGenerator = context.getBean(ConfigurationGenerator.class);
Set<String> availableConfigs = new HashSet<>(configurationGenerator.getJaggerLoadScenarioNames());
context.destroy();
return availableConfigs;
}
private static String doLaunchMaster(final URL directory, final ClassLoader classLoader) {
AbstractXmlApplicationContext context = loadContext(directory, MASTER_CONFIGURATION, environmentProperties, classLoader);
initCoordinator(context);
context.getBean(StorageServerLauncher.class); // to trigger lazy initialization
ConfigurationGenerator configurationGenerator = context.getBean(ConfigurationGenerator.class);
log.info("Going to execute {} load scenario...", environmentProperties.getProperty(LOAD_SCENARIO_ID_PROP));
configurationGenerator.setJLoadScenarioIdToExecute(environmentProperties.getProperty(LOAD_SCENARIO_ID_PROP));
WorkloadTaskDistributor workloadTaskDistributor = context.getBean(WorkloadTaskDistributor.class);
workloadTaskDistributor.setClassesUrl(environmentProperties.getProperty(LOAD_SCENARIO_CLASSES_URL));
Master master = context.getBean(Master.class);
master.run();
String sessionId = master.getSessionIdProvider().getSessionId();
context.destroy();
return sessionId;
}
private static String doLaunchMaster(final URL directory) {
return doLaunchMaster(directory, JaggerLauncher.class.getClassLoader());
}
private static void launchReporter(final URL directory) {
LaunchTask launchReporter = new LaunchTask() {
@Override
public void run() {
ApplicationContext context = loadContext(directory, REPORTER_CONFIGURATION, environmentProperties);
final ReportingService reportingService = (ReportingService) context.getBean("reportingService");
reportingService.renderReport(true);
}
};
builder.addMainTask(launchReporter);
}
private static void launchKernel(final URL directory) {
LaunchTask runKernel = new LaunchTask() {
private Kernel kernel;
@Override
public void run() {
log.info("Starting Kernel");
ApplicationContext context = loadContext(directory, KERNEL_CONFIGURATION, environmentProperties);
final CountDownLatch latch = new CountDownLatch(1);
final Coordinator coordinator = (Coordinator) context.getBean("coordinator");
kernel = (Kernel) context.getBean("kernel");
toTerminate(kernel);
Runnable kernelRunner = () -> {
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
kernel.run();
};
getExecutor().execute(kernelRunner);
coordinator.waitForReady();
coordinator.waitForInitialization();
latch.countDown();
}
};
builder.addBackgroundTask(runKernel);
}
private static void launchRdbServer(final URL directory) {
log.info("Starting RDB Server");
LaunchTask rdbRunner = new LaunchTask() {
@Override
public void run() {
ApplicationContext context = loadContext(directory, RDB_CONFIGURATION, environmentProperties);
H2DatabaseServer dbServer = (H2DatabaseServer) context.getBean("databaseServer");
dbServer.run();
}
};
builder.addBackgroundTask(rdbRunner);
}
private static void launchCoordinationServer(final URL directory) {
LaunchTask zookeeperInitializer = new LaunchTask() {
// private ZooKeeperServer zooKeeper;
private AttendantServer server;
public void run() {
log.info("Starting Coordination Server");
ApplicationContext context = loadContext(directory, COORDINATION_CONFIGURATION, environmentProperties);
server = (AttendantServer) context.getBean("coordinatorServer");
toTerminate(server);
getExecutor().execute(server);
server.initialize();
}
};
builder.addMainTask(zookeeperInitializer);
}
private static void launchCometdCoordinationServer(final URL directory) {
LaunchTask jettyRunner = new LaunchTask() {
public void run() {
log.info("Starting Cometd Coordination Server");
ApplicationContext context =
loadContext(directory, COORDINATION_HTTP_CONFIGURATION, environmentProperties);
initCoordinator(context);
Server jettyServer = (Server) context.getBean("jettyServer");
try {
jettyServer.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
builder.addMainTask(jettyRunner);
}
public static AbstractXmlApplicationContext
loadContext(URL directory, String role, Properties environmentProperties, ClassLoader classLoader) {
String[] includePatterns = StringUtils.split(environmentProperties.getProperty(role + INCLUDE_SUFFIX), ", ");
String[] excludePatterns = StringUtils.split(environmentProperties.getProperty(role + EXCLUDE_SUFFIX), ", ");
List<String> descriptors = discoverResources(directory, includePatterns, excludePatterns);
log.info("Discovered descriptors:");
for (String descriptor : descriptors) {
log.info(" " + descriptor);
}
return new JaggerXmlApplicationContext(
directory, environmentProperties, descriptors.toArray(new String[descriptors.size()]), classLoader);
}
public static AbstractXmlApplicationContext loadContext(URL directory, String role, Properties environmentProperties) {
return loadContext(directory, role, environmentProperties, JaggerLauncher.class.getClassLoader());
}
private static List<String> discoverResources(URL directory, String[] includePatterns, String[] excludePatterns) {
PathMatchingResourcePatternResolver resolver =
new PathMatchingResourcePatternResolver(new FileSystemResourceLoader());
List<String> resourceNames = new ArrayList<>();
PathMatcher matcher = new AntPathMatcher();
try {
for (String pattern : includePatterns) {
Resource[] includeResources = resolver.getResources(directory.toString() + pattern);
for (Resource resource : includeResources) {
boolean isValid = true;
for (String excludePattern : excludePatterns) {
if (matcher.match(excludePattern, resource.getFilename())) {
isValid = false;
break;
}
}
if (isValid) {
resourceNames.add(resource.getURI().toString());
}
}
}
} catch (IOException e) {
throw new TechnicalException(e);
}
return resourceNames;
}
public static void loadBootProperties(URL directory, String environmentPropertiesLocation,
Properties environmentProperties
) throws IOException {
// priorities
// low priority (default properties - environment props - user props - system properties) high priority
// properties from command line - environment properties
URL bootPropertiesFile = new URL(directory, environmentPropertiesLocation);
System.setProperty(ENVIRONMENT_PROPERTIES, environmentPropertiesLocation);
environmentProperties.load(bootPropertiesFile.openStream());
// user properties
String userBootPropertiesLocationsString = System.getProperty(USER_ENVIRONMENT_PROPERTIES);
if (userBootPropertiesLocationsString == null) {
userBootPropertiesLocationsString = environmentProperties.getProperty(USER_ENVIRONMENT_PROPERTIES);
}
if (userBootPropertiesLocationsString == null) {
userBootPropertiesLocationsString = DEFAULT_USER_ENVIRONMENT_PROPERTIES_LOCATION;
}
String[] userBootPropertiesSingleLocations = userBootPropertiesLocationsString.split(",");
for (String location : userBootPropertiesSingleLocations) {
URL userBootPropertiesFile = new URL(directory, location);
Properties userBootProperties = new Properties();
userBootProperties.load(userBootPropertiesFile.openStream());
for (String name : userBootProperties.stringPropertyNames()) {
// overwrite due to higher priority
environmentProperties.setProperty(name, userBootProperties.getProperty(name));
}
}
// default properties
String defaultBootPropertiesLocation = System.getProperty(DEFAULT_ENVIRONMENT_PROPERTIES);
if (defaultBootPropertiesLocation == null) {
defaultBootPropertiesLocation = environmentProperties.getProperty(DEFAULT_ENVIRONMENT_PROPERTIES);
}
if (defaultBootPropertiesLocation == null) {
defaultBootPropertiesLocation = DEFAULT_ENVIRONMENT_PROPERTIES_LOCATION;
}
URL defaultBootPropertiesFile = new URL(directory, defaultBootPropertiesLocation);
Properties defaultBootProperties = new Properties();
defaultBootProperties.load(defaultBootPropertiesFile.openStream());
for (String name : defaultBootProperties.stringPropertyNames()) {
// only append due to low priority
if (!environmentProperties.containsKey(name)) {
environmentProperties.setProperty(name, defaultBootProperties.getProperty(name));
}
}
Properties properties = System.getProperties();
for (Enumeration<String> enumeration = (Enumeration<String>) properties.propertyNames();
enumeration.hasMoreElements(); ) {
String key = enumeration.nextElement();
// overwrite due to higher priority
environmentProperties.put(key, properties.get(key));
}
System.setProperty(ENVIRONMENT_PROPERTIES, environmentPropertiesLocation);
System.setProperty(USER_ENVIRONMENT_PROPERTIES, userBootPropertiesLocationsString);
System.setProperty(DEFAULT_ENVIRONMENT_PROPERTIES, defaultBootPropertiesLocation);
}
}