/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2012
* (in the framework of the ALMA collaboration).
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package acs.benchmark.nc.client;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import acs.benchmark.util.ContainerUtil;
import acs.benchmark.util.ContainerUtil.ContainerLogLevelSpec;
import alma.ACS.ACSComponentOperations;
import alma.ACSErrTypeCommon.CouldntPerformActionEx;
import alma.ACSErrTypeCommon.wrappers.AcsJCouldntPerformActionEx;
import alma.acs.component.client.ComponentClient;
import alma.acs.logging.level.AcsLogLevelDefinition;
import alma.acs.pubsubtest.config.ContainerSpecT;
import alma.acs.pubsubtest.config.PubSubInfrastructureSpec;
import alma.acs.pubsubtest.config.PubSubSpecCommonT;
import alma.acs.pubsubtest.config.PublisherSpecT;
import alma.acs.pubsubtest.config.SimpleLoggingSpecT;
import alma.acs.pubsubtest.config.SubscriberSpecT;
import alma.acs.pubsubtest.config.TerminationSpecT;
import alma.acs.pubsubtest.config.types.EventNameT;
import alma.acs.pubsubtest.config.types.ImplLangT;
import alma.acs.util.AcsLocations;
import alma.benchmark.CorbaNotifyCompBaseOperations;
import alma.benchmark.CorbaNotifyConsumerOperations;
import alma.benchmark.CorbaNotifySupplierOperations;
import alma.benchmark.NcEventSpec;
import alma.maci.containerconfig.types.ContainerImplLangType;
/**
* Creates and runs publisher and subscriber components as specified
* in an XML configuration file that must comply with schema pubSubConfig.xsd.
* <p>
* About termination of pub-sub runs:
* <ol>
* <li>The optional xml attribute <code>numberOfEvents</code> can control
* how many events a publisher sends or a subscriber receives.
* This is useful only when these times are short, since this PubSubExecutor will
* keep open calls to these components.
* <li>If attribute <code>numberOfEvents</code> is missing or has a non-positive value,
* then the PubSubExecutor commands all publisher and subscriber
* components to do their work, and the calls to these components return.
* This makes sense for longer times.
* Therefore, if at least one pub/sub component is configured for an unbounded number of events,
* the method {@link #execute(PubSubScenario, long, TimeUnit)}
* must be called with a timeout, so that the pub/sub components can be stopped
* and their containers as well.
* </ol>
*
* @author hsommer
*/
public class PubSubExecutor extends ComponentClient
{
/**
* Use values != null to cheat, e.g. when running a test from Eclipse on Windows
* but the containers should be somewhere else.
*/
private static final String localhostName = "alma-head"; //= null;
private final ContainerUtil containerUtil;
private final PubSubComponentAccessUtil componentAccessUtil;
private final SimpleLoggingSpecT defaultLoggingSpec;
/**
* Constructor, which is independent of a concrete pub-sub scenario.
* This allows one <code>PubSubExecutor</code> to execute more than one scenario.
*
* @throws Exception
*/
public PubSubExecutor() throws Exception {
super(null, AcsLocations.figureOutManagerLocation(), PubSubExecutor.class.getSimpleName());
containerUtil = new ContainerUtil(getContainerServices());
containerUtil.loginToManager();
componentAccessUtil = new PubSubComponentAccessUtil(getContainerServices());
defaultLoggingSpec = new SimpleLoggingSpecT();
defaultLoggingSpec.setDefaultLevelMin(4);
defaultLoggingSpec.setDefaultLevelMinLocal(4);
defaultLoggingSpec.setJacorbLevelMin(4);
}
/**
* This method must be called when done, so that this <code>PubSubExecutor</code>
* can release resources.
* @see alma.acs.component.client.ComponentClient#tearDown()
*/
public void tearDown() throws Exception {
if (componentAccessUtil != null) {
componentAccessUtil.releaseAllComponents(true);
}
if (containerUtil != null) {
// TODO: create and call a new method containerUtil.stopAllContainers
containerUtil.logoutFromManager();
}
super.tearDown();
}
/**
* Run the specified pub/sub container and components.
* Since no timeout is passed to this method, one of the following must be true:
* <ul>
* <li>The XML spec contains the (optional) <code>Termination</code> element
* that defines a timeout, or
* <li>No pub/sub component has an unbounded number of events, which means
* that all component definitions must contain the <code>numberOfEvents</code> attribute.
* </ul>
* @param scenario
* @throws Throwable
*/
public void execute(PubSubScenario scenario) throws Throwable {
PubSubInfrastructureSpec pubSubSpec = scenario.getSpec();
TerminationSpecT teminationSpec = pubSubSpec.getTermination();
if (teminationSpec == null) {
for (PublisherSpecT pubSpec : pubSubSpec.getPublisher()) {
if (!pubSpec.hasNumberOfEvents() || pubSpec.getNumberOfEvents() <= 0) {
throw new IllegalArgumentException("Publisher " + pubSpec.getComponentName()
+ " needs numberOfEvents >= 0, or call 'execute' with a timeout.");
}
}
for (SubscriberSpecT subSpec : pubSubSpec.getSubscriber()) {
if (!subSpec.hasNumberOfEvents() || subSpec.getNumberOfEvents() <= 0) {
throw new IllegalArgumentException("Subscriber " + subSpec.getComponentName()
+ " needs numberOfEvents >= 0, or call 'execute' with a timeout.");
}
}
// timeout is implied by the finite number of events
execute(scenario, -1, null);
}
else {
// use timeout from XML, complementary to any finite number of events if those are specified.
long timeout = teminationSpec.getTimeout();
TimeUnit timeUnit = TimeUnit.valueOf(teminationSpec.getTimeUnit().toString());
m_logger.fine("Will use timeout from the XML spec: timeout=" + timeout + ", timeUnit=" + timeUnit);
execute(scenario, timeout, timeUnit);
}
}
/**
* Run the specified pub/sub container and components.
* If this method gets called directly (and not by {@link #execute(PubSubScenario)},
* the executionTime parameter can overwrite the timeout setting from the XML, or can
* set a timeout complementary to a finite <code>PubSubSpecCommonT#numberOfEvents</code>.
*/
public void execute(PubSubScenario scenario, long executionTimeMax, TimeUnit executionTimeMaxUnit) throws Throwable {
PubSubInfrastructureSpec pubSubSpec = scenario.getSpec();
m_logger.info("Will execute test with description:\n" + pubSubSpec.getTestDescription());
SimpleLoggingSpecT loggingSpec = ( pubSubSpec.getLogging() != null ? pubSubSpec.getLogging() : defaultLoggingSpec );
AcsLogLevelDefinition levelDefaultLocal = AcsLogLevelDefinition.fromInteger(loggingSpec.getDefaultLevelMinLocal());
AcsLogLevelDefinition levelDefault = AcsLogLevelDefinition.fromInteger(loggingSpec.getDefaultLevelMin());
AcsLogLevelDefinition levelJacORB = AcsLogLevelDefinition.fromInteger(loggingSpec.getJacorbLevelMin());
List<Throwable> errors = new ArrayList<Throwable>();
try {
for (ContainerSpecT containerSpec : pubSubSpec.getContainer()) {
// start containers sequentially (TODO: with thread pool)
String host = ( containerSpec.getHostName() != null ? containerSpec.getHostName() : localhostName );
String containerName = containerSpec.getContainerName();
m_logger.fine("about to start container " + containerName + " on host " + (host==null ? "localhost" : host));
ContainerImplLangType implLang = ContainerImplLangType.valueOf(containerSpec.getImplLang().toString());
containerUtil.startContainer(host, implLang, containerName, null, true);
// configure container log levels
ContainerLogLevelSpec contLogLevelSpec = new ContainerLogLevelSpec(levelDefault, levelDefaultLocal);
contLogLevelSpec.addNamedLoggerSpec("jacorb@"+containerName, levelJacORB, levelJacORB);
containerUtil.setContainerLogLevels(containerName, contLogLevelSpec);
m_logger.info("started container " + containerName + " on host " + (host==null ? "localhost" : host));
}
} catch (Throwable thr) {
errors.add(thr);
}
boolean allPubSubHaveFiniteEvents = true;
ExecutorService pubSubExec = Executors.newCachedThreadPool(getContainerServices().getThreadFactory());
if (errors.isEmpty()) {
for (final SubscriberSpecT subSpec : pubSubSpec.getSubscriber()) {
boolean subIsBlocking = isBlocking(subSpec);
if (!subIsBlocking) {
allPubSubHaveFiniteEvents = false;
}
ImplLangT implLang = deriveComponentImplLang(subSpec, pubSubSpec);
// Get the subscriber component. This may retrieve the same component more than once if it should use more than one NC
// (which will result in multiple NCSubscriber instances created by that component).
// TODO: pack this into one call, now that the comp will fail on the second call !!!
if (componentAccessUtil.getCachedComponentNames().contains(subSpec.getComponentName())) {
m_logger.info("Multiple subscribers specified for component " + subSpec.getComponentName() + ". This is legal, but unusual. Some values may be overwritten.");
}
CorbaNotifyConsumerOperations subscriberComp =
componentAccessUtil.getDynamicSubscriberComponent(subSpec.getComponentName(), subSpec.getContainerName(), implLang);
if (subSpec.hasLogMultiplesOfEventCount()) {
subscriberComp.setEventLogging(subSpec.getLogMultiplesOfEventCount());
}
PubSubRunner runner = new PubSubRunner(subSpec, subscriberComp, pubSubExec, m_logger) {
@Override
protected Integer callSpecific(NcEventSpec eventSpec, int numEvents) throws CouldntPerformActionEx {
logger.info("About to call subscriber#receiveEvents...");
int processingDelay = (subSpec.hasProcessingDelayMillis() ? subSpec.getProcessingDelayMillis() : -1);
return ((CorbaNotifyConsumerOperations)pubSubComp).receiveEvents(new NcEventSpec[] {eventSpec}, processingDelay, numEvents);
}
};
try {
runner.runPubSub();
} catch (Exception ex) {
errors.add(ex);
}
}
Thread.sleep(100); // to "ensure" that even the asynchronously called subscribers are ready before we publish events
for (final PublisherSpecT pubSpec : pubSubSpec.getPublisher()) {
boolean pubIsBlocking = isBlocking(pubSpec);
if (!pubIsBlocking) {
allPubSubHaveFiniteEvents = false;
}
ImplLangT implLang = deriveComponentImplLang(pubSpec, pubSubSpec);
// Get the publisher component. This may retrieve the same component more than once if it should use more than one NC
// (which will result in multiple NCPublisher instances created by that component).
if (componentAccessUtil.getCachedComponentNames().contains(pubSpec.getComponentName())) {
m_logger.info("Multiple publishers specified for component " + pubSpec.getComponentName() + ". This is legal, but unusual. Some values may be overwritten.");
}
CorbaNotifySupplierOperations publisherComp =
componentAccessUtil.getDynamicSupplierComponent(pubSpec.getComponentName(), pubSpec.getContainerName(), implLang);
if (pubSpec.hasLogMultiplesOfEventCount()) {
publisherComp.setEventLogging(pubSpec.getLogMultiplesOfEventCount());
}
PubSubRunner runner = new PubSubRunner(pubSpec, publisherComp, pubSubExec, m_logger) {
@Override
protected Integer callSpecific(NcEventSpec eventSpec, int numEvents) throws CouldntPerformActionEx {
logger.info("About to call publisher#sendEvents...");
int eventPeriodMillis = (pubSpec.hasEventPeriodMillis() ? pubSpec.getEventPeriodMillis() : -1);
return ((CorbaNotifySupplierOperations)pubSubComp).sendEvents(new NcEventSpec[] {eventSpec}, eventPeriodMillis, numEvents);
}
};
try {
runner.runPubSub();
} catch (Exception ex) {
errors.add(ex);
}
}
} // end of starting all pubs/subs
// wait for NC scenario to execute
if (errors.isEmpty()) {
if (allPubSubHaveFiniteEvents) {
// wait for all suppliers and subscribers to finish, enforcing the timeout if applicable
// TODO: More options would be available if we stored the PubSubRunner instances in a list...
pubSubExec.shutdown();
if (executionTimeMax > 0) {
pubSubExec.awaitTermination(executionTimeMax, executionTimeMaxUnit);
}
else {
pubSubExec.awaitTermination(100*365, TimeUnit.DAYS); // like Dornroeschen
}
}
else {
// executionTime should be used to let the "indefinite" suppliers and subscribers do their work,
// even if some others should terminate by themselves
if (executionTimeMax > 0) {
try {
m_logger.info("Will sleep for " + executionTimeMax + " " + executionTimeMaxUnit.toString().toLowerCase() + "...");
executionTimeMaxUnit.sleep(executionTimeMax);
} catch (Exception ex) {
errors.add(ex);
}
// now also the finite suppliers/subscribers should be finished, but even if not they will get interrupted
// along with the infinite ones in the cleanup.
}
else {
errors.add(new IllegalArgumentException("An execution time must be specified if some publisher or subscriber is configured with an infinite number of events. Terminating right away..."));
}
}
}
// cleanup
m_logger.info("Will clean up the pub/sub components...");
// interrupt and release all components
for (ACSComponentOperations comp : componentAccessUtil.getCachedComponents()) {
// we could avoid the casting if we keep a separate list of subscriber and supplier comps...
if (comp instanceof CorbaNotifyCompBaseOperations) {
CorbaNotifyCompBaseOperations pubSubComp = (CorbaNotifyCompBaseOperations) comp;
pubSubComp.ncDisconnect();
}
}
componentAccessUtil.releaseAllComponents(true);
// stop containers
for (ContainerSpecT containerSpec : pubSubSpec.getContainer()) {
try {
String host = ( containerSpec.getHostName() != null ? containerSpec.getHostName() : localhostName );
String containerName = containerSpec.getContainerName();
m_logger.fine("about to stop container " + containerName + " on host " + (host==null ? "localhost" : host));
containerUtil.stopContainer(host, containerName);
m_logger.info("stopped container " + containerName + " on host " + (host==null ? "localhost" : host));
} catch (Throwable thr) {
errors.add(thr);
}
}
if (!errors.isEmpty()) {
m_logger.severe("There were " + errors.size() + " errors!");
throw errors.get(0);
}
}
private ImplLangT deriveComponentImplLang(PubSubSpecCommonT spec, PubSubInfrastructureSpec infraSpec) {
ImplLangT implLang = null; // we derive the component implementation language from the associated container
for (ContainerSpecT containerSpec : infraSpec.getContainer()) {
if (containerSpec.getContainerName().equals(spec.getContainerName())) {
implLang = containerSpec.getImplLang();
}
}
return implLang;
}
private static boolean isBlocking(PubSubSpecCommonT spec) {
return ( spec.hasNumberOfEvents() && spec.getNumberOfEvents() > 0 );
}
/**
* Code shared between starting a single supplier or a subscriber for a given NC and component.
* The user should call {@link #runPubSub()}.
* Also encapsulates the decision whether to run a non-blocking call in the same thread
* or to run in a different thread using the provided ExecutorService.
*/
private static abstract class PubSubRunner implements Callable<Integer> {
protected final Logger logger;
private final PubSubSpecCommonT spec;
protected final CorbaNotifyCompBaseOperations pubSubComp;
private final ExecutorService pubSubExec;
private Future<Integer> callFuture; // could be useful in the future...
PubSubRunner(PubSubSpecCommonT spec, CorbaNotifyCompBaseOperations pubSubComp, ExecutorService pubSubExec, Logger logger) {
this.logger = logger;
this.spec = spec;
this.pubSubComp = pubSubComp;
this.pubSubExec = pubSubExec;
}
void runPubSub() throws Exception {
if (isBlocking(spec)) {
// the publisher / subscriber will return only when the specified number of events have been published / received,
// for which we need to run it from a separate thread and later sync on the returned Future.
callFuture = pubSubExec.submit(this);
}
else {
// just call "sendEvents"/ "receiveEvents" from the current thread, as the call will return immediately
call();
}
}
@Override
public Integer call() throws Exception {
try {
pubSubComp.ncConnect(new String[] {spec.getNC()} );
} catch (CouldntPerformActionEx ex) {
AcsJCouldntPerformActionEx ex2 = AcsJCouldntPerformActionEx.fromCouldntPerformActionEx(ex);
logger.log(Level.WARNING, "Failure invoking " + pubSubComp.name() + "#ncConnect: ", ex2);
throw ex2;
}
logger.info("Connected '" + spec.getComponentName() + "' to NC " + spec.getNC());
List<String> eventNamesForIdl = new ArrayList<String>();
for (EventNameT eventName : spec.getEventName()) {
eventNamesForIdl.add(eventName.toString());
}
final NcEventSpec eventSpec = new NcEventSpec(spec.getNC(), eventNamesForIdl.toArray(new String[0]), "");
int numEvents = (spec.hasNumberOfEvents() ? spec.getNumberOfEvents() : -1);
return callSpecific(eventSpec, numEvents);
}
/**
* Subclasses must contribute pub / sub specific code here, to make the actual component call.
*/
abstract Integer callSpecific(NcEventSpec eventSpec, int numEvents) throws CouldntPerformActionEx;
}
/**
* Parameters:
* <ol>
* <li>XML config file name.
* <li>Optional container-host mapping in the form <code>container1=host1:container2=host2</code>.
* </ol>
* @param args
*/
public static void main(String[] args) {
if (args.length < 1 || args.length > 2) {
throw new IllegalArgumentException("Expecting 1 or 2 arguments: <XML config file> [<container1=host1:>]");
}
PubSubExecutor exec = null;
try {
exec = new PubSubExecutor();
Logger logger = exec.getContainerServices().getLogger();
File xmlFile = new File(args[0]);
PubSubScenario scenario = new PubSubScenario(logger, xmlFile, true);
if (args.length == 2) {
scenario.setHostNames(args[1]);
}
else {
logger.fine("No container-host mapping specified, will use localhost for all containers.");
}
exec.execute(scenario);
} catch (Throwable thr) {
thr.printStackTrace();
} finally {
if (exec != null) {
try {
exec.tearDown();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}