package org.radargun; import java.io.IOException; import java.util.Map; import org.radargun.config.Cluster; import org.radargun.config.Configuration; import org.radargun.config.InitHelper; import org.radargun.config.PropertyHelper; import org.radargun.config.Scenario; import org.radargun.logging.Log; import org.radargun.logging.LogFactory; import org.radargun.reporting.Timeline; import org.radargun.stages.ScenarioCleanupStage; import org.radargun.state.SlaveState; import org.radargun.traits.TraitHelper; import org.radargun.utils.TimeService; /** * Base class for both standalone slave and slave integrated in master node (local cluster). * * @author Radim Vansa <rvansa@redhat.com> */ public abstract class SlaveBase { protected final Log log = LogFactory.getLog(getClass()); protected SlaveState state = new SlaveState(); protected Configuration configuration; protected Cluster cluster; protected Scenario scenario; protected void scenarioLoop() throws IOException { Cluster.Group group = cluster.getGroup(state.getSlaveIndex()); Configuration.Setup setup = configuration.getSetup(group.name); state.setCluster(cluster); state.setPlugin(setup.plugin); state.setService(setup.service); state.setTimeline(new Timeline(state.getSlaveIndex())); Map<String, String> extras = getCurrentExtras(configuration, cluster); ServiceHelper.setServiceContext(setup.plugin, configuration.name, state.getSlaveIndex()); Object service = ServiceHelper.createService(setup.plugin, setup.service, setup.getProperties(), extras); Map<Class<?>, Object> traits = null; try { log.info("Service is " + service.getClass().getSimpleName() + PropertyHelper.toString(service)); traits = TraitHelper.retrieve(service); state.setTraits(traits); for (;;) { int stageId = getNextStageId(); Map<String, Object> masterData = getNextMasterData(); for (Map.Entry<String, Object> entry : masterData.entrySet()) { state.put(entry.getKey(), entry.getValue()); } log.trace("Received stage ID " + stageId); DistStage stage = (DistStage) scenario.getStage(stageId, state, extras, null); if (stage instanceof ScenarioCleanupStage) { // this is always the last stage and is ran in main thread (not sc-main) break; } TraitHelper.InjectResult result = null; DistStageAck response; Exception initException = null; try { result = TraitHelper.inject(stage, traits); InitHelper.init(stage); stage.initOnSlave(state); } catch (Exception e) { log.error("Stage '" + stage.getName() + "' initialization has failed", e); initException = e; } if (initException != null) { response = new DistStageAck(state).error("Stage '" + stage.getName() + "' initialization has failed", initException); } else if (!stage.shouldExecute()) { log.info("Stage '" + stage.getName() + "' should not be executed"); response = new DistStageAck(state); } else if (result == TraitHelper.InjectResult.SKIP) { log.info("Stage '" + stage.getName() + "' was skipped because it was missing some traits"); response = new DistStageAck(state); } else if (result == TraitHelper.InjectResult.FAILURE) { String message = "The stage '" + stage.getName() + "' was not executed because it missed some mandatory traits."; log.error(message); response = new DistStageAck(state).error(message, null); } else { String stageName = stage.getName(); log.info("Starting stage " + (log.isDebugEnabled() ? stage.toString() : stageName)); long start = TimeService.currentTimeMillis(); long end; try { response = stage.executeOnSlave(); end = TimeService.currentTimeMillis(); if (response == null) { response = new DistStageAck(state).error("Stage returned null response", null); } log.info("Finished stage " + stageName); response.setDuration(end - start); } catch (Exception e) { end = TimeService.currentTimeMillis(); log.error("Stage execution has failed", e); response = new DistStageAck(state).error("Stage execution has failed", e); } finally { InitHelper.destroy(stage); } state.getTimeline().addEvent(Stage.STAGE, new Timeline.IntervalEvent(start, stageName, end - start)); } sendResponse(response); } } finally { if (traits != null) { for (Object trait : traits.values()) { InitHelper.destroy(trait); } } InitHelper.destroy(service); } } protected abstract int getNextStageId() throws IOException; protected abstract Map<String, Object> getNextMasterData() throws IOException; protected abstract void sendResponse(DistStageAck response) throws IOException; protected void runCleanup() throws IOException { DistStageAck response = null; try { Map<String, String> extras = getCurrentExtras(configuration, cluster); ScenarioCleanupStage stage = (ScenarioCleanupStage) scenario.getStage(scenario.getStageCount() - 1, state, extras, null); InitHelper.init(stage); stage.initOnSlave(state); log.info("Starting stage " + (log.isDebugEnabled() ? stage.toString() : stage.getName())); response = stage.executeOnSlave(); } catch (Exception e) { log.error("Stage execution has failed", e); response = new DistStageAck(state).error("Stage execution has failed", e); } finally { if (response == null) { response = new DistStageAck(state).error("Stage returned null response", null); } sendResponse(response); } } protected abstract Map<String, String> getCurrentExtras(Configuration configuration, Cluster cluster); // In RadarGun 2.0, we had to run each service in new thread in order to prevent // classloader leaking through thread locals. This is not necessary anymore, // but we still do checks in ScenarioCleanupStage protected class ScenarioRunner extends Thread { protected ScenarioRunner() { super("sc-main"); } @Override public void run() { try { scenarioLoop(); } catch (IOException e) { log.error("Communication with master failed", e); e.printStackTrace(); ShutDownHook.exit(127); } catch (Throwable t) { log.error("Unexpected error in scenario", t); t.printStackTrace(); ShutDownHook.exit(127); } } } }