From df9b160b881fa38961c0e6afdc0978f2efaa057d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20R=C3=B6der?= Date: Fri, 8 Dec 2023 18:11:50 +0100 Subject: [PATCH] Added an improved error reporting to the components on two levels: 1. in the single abstract component implementations and in the ComponentStarter. The latter checks whether the component can report errors and tries to call the report method in case of an error. --- src/main/java/org/hobbit/core/Commands.java | 40 +- .../AbstractBenchmarkController.java | 108 +++--- .../AbstractCommandReceivingComponent.java | 357 +++++++++++------- .../components/AbstractDataGenerator.java | 24 +- .../components/AbstractEvaluationModule.java | 81 ++-- .../components/AbstractSystemAdapter.java | 52 +-- .../components/AbstractTaskGenerator.java | 73 ++-- .../java/org/hobbit/core/data/ErrorData.java | 89 ++++- .../hobbit/core/data/ReportedException.java | 26 ++ .../org/hobbit/core/run/ComponentStarter.java | 11 +- .../java/org/hobbit/vocab/HobbitErrors.java | 2 + 11 files changed, 520 insertions(+), 343 deletions(-) create mode 100644 src/main/java/org/hobbit/core/data/ReportedException.java diff --git a/src/main/java/org/hobbit/core/Commands.java b/src/main/java/org/hobbit/core/Commands.java index 87c395f..372aff7 100644 --- a/src/main/java/org/hobbit/core/Commands.java +++ b/src/main/java/org/hobbit/core/Commands.java @@ -33,8 +33,8 @@ private Commands() { */ public static final byte SYSTEM_READY_SIGNAL = 1; /** - * The signal sent by the benchmark controller to indicate that the - * benchmark is ready. + * The signal sent by the benchmark controller to indicate that the benchmark is + * ready. */ public static final byte BENCHMARK_READY_SIGNAL = 2; /** @@ -64,11 +64,9 @@ private Commands() { public static final byte BENCHMARK_FINISHED_SIGNAL = 11; /** - * Command used to ask a docker managing component to start a certain - * container. + * Command used to ask a docker managing component to start a certain container. *

- * The command is followed by a String containing the following JSON data: - *
+ * The command is followed by a String containing the following JSON data:
* * {
"image": "image-to-run",
"type": "system|benchmark",
"parent":"parent-container-id"
} *
@@ -76,11 +74,9 @@ private Commands() { */ public static final byte DOCKER_CONTAINER_START = 12; /** - * Command used to ask a docker managing component to stop a certain - * container. + * Command used to ask a docker managing component to stop a certain container. *

- * The command is followed by a String containing the following JSON data: - *
+ * The command is followed by a String containing the following JSON data:
* * {
"containerId": "container-to-stop"
} *
@@ -95,8 +91,27 @@ private Commands() { public static final byte DOCKER_CONTAINER_TERMINATED = 16; public static final byte START_BENCHMARK_SIGNAL = 17; - + public static final byte REQUEST_SYSTEM_RESOURCES_USAGE = 18; + /** + * Command used to report an error that should be persisted as part of the + * experiment result data. + *

+ * The command is followed by a String containing the following JSON data:
+ * + * {
"containerId": "container reporting the error", + *
"errorType": "IRI of the error type (optional)", + *
"label": "A string that can be used as short label of an error (optional, the error type label will be used as default)" + *
"description": "A string that can be used as a short description of an error (optional, the error type description will be used as default)" + *
"details": "A string that contains details about the error, e.g., a stack trace (optional)." + *
} + *
+ *

+ *

+ * The {@link org.hobbit.core.data.ErrorData} class can be used to represent + * this data as Java object.

+ */ + public static final byte REPORT_ERROR = 19; private static final ImmutableMap ID_TO_COMMAND_NAME_MAP = generateMap(); @@ -116,7 +131,8 @@ private static ImmutableMap generateMap() { } /** - * Returns the name of the command if it is defined inside the {@link Commands} class or its id as String. + * Returns the name of the command if it is defined inside the {@link Commands} + * class or its id as String. * * @param command the command that should be transformed into a String * @return the name of the command or its id if the name is not known diff --git a/src/main/java/org/hobbit/core/components/AbstractBenchmarkController.java b/src/main/java/org/hobbit/core/components/AbstractBenchmarkController.java index 5f53737..798fcfa 100644 --- a/src/main/java/org/hobbit/core/components/AbstractBenchmarkController.java +++ b/src/main/java/org/hobbit/core/components/AbstractBenchmarkController.java @@ -156,24 +156,25 @@ public void init() throws Exception { @Override public void run() throws Exception { - sendToCmdQueue(Commands.BENCHMARK_READY_SIGNAL); - // wait for the start signal - startBenchmarkMutex.acquire(); - executeBenchmark(); + try { + sendToCmdQueue(Commands.BENCHMARK_READY_SIGNAL); + // wait for the start signal + startBenchmarkMutex.acquire(); + executeBenchmark(); + } catch (Exception e) { + throw reportAndWrap(e); + } } protected abstract void executeBenchmark() throws Exception; /** - * Creates the given number of data generators using the given image name - * and environment variables. + * Creates the given number of data generators using the given image name and + * environment variables. * - * @param dataGeneratorImageName - * name of the data generator Docker image - * @param numberOfDataGenerators - * number of generators that should be created - * @param envVariables - * environment variables for the data generators + * @param dataGeneratorImageName name of the data generator Docker image + * @param numberOfDataGenerators number of generators that should be created + * @param envVariables environment variables for the data generators */ protected void createDataGenerators(String dataGeneratorImageName, int numberOfDataGenerators, String[] envVariables) { @@ -181,15 +182,12 @@ protected void createDataGenerators(String dataGeneratorImageName, int numberOfD } /** - * Creates the given number of task generators using the given image name - * and environment variables. + * Creates the given number of task generators using the given image name and + * environment variables. * - * @param taskGeneratorImageName - * name of the task generator Docker image - * @param numberOfTaskGenerators - * number of generators that should be created - * @param envVariables - * environment variables for the task generators + * @param taskGeneratorImageName name of the task generator Docker image + * @param numberOfTaskGenerators number of generators that should be created + * @param envVariables environment variables for the task generators */ protected void createTaskGenerators(String taskGeneratorImageName, int numberOfTaskGenerators, String[] envVariables) { @@ -199,14 +197,10 @@ protected void createTaskGenerators(String taskGeneratorImageName, int numberOfT /** * Internal method for creating generator components. * - * @param generatorImageName - * name of the generator Docker image - * @param numberOfGenerators - * number of generators that should be created - * @param envVariables - * environment variables for the task generators - * @param generatorIds - * set of generator container names + * @param generatorImageName name of the generator Docker image + * @param numberOfGenerators number of generators that should be created + * @param envVariables environment variables for the task generators + * @param generatorIds set of generator container names */ private void createGenerator(String generatorImageName, int numberOfGenerators, String[] envVariables, Set generatorIds) { @@ -216,7 +210,8 @@ private void createGenerator(String generatorImageName, int numberOfGenerators, // NOTE: Count only includes generators created within this method call. variables[variables.length - 2] = Constants.GENERATOR_COUNT_KEY + "=" + numberOfGenerators; for (int i = 0; i < numberOfGenerators; ++i) { - // At the start generatorIds is empty, and new generators are added to it immediately. + // At the start generatorIds is empty, and new generators are added to it + // immediately. // Current size of that set is used to make IDs for new generators. variables[variables.length - 1] = Constants.GENERATOR_ID_KEY + "=" + generatorIds.size(); containerId = createContainer(generatorImageName, variables); @@ -234,10 +229,9 @@ private void createGenerator(String generatorImageName, int numberOfGenerators, * Creates the evaluate module using the given image name and environment * variables. * - * @param evalModuleImageName - * name of the evaluation module image - * @param envVariables - * environment variables that should be given to the module + * @param evalModuleImageName name of the evaluation module image + * @param envVariables environment variables that should be given to the + * module */ protected void createEvaluationModule(String evalModuleImageName, String[] envVariables) { envVariables = ArrayUtils.add(envVariables, Constants.HOBBIT_EXPERIMENT_URI_KEY + "=" + experimentUri); @@ -263,10 +257,9 @@ protected void createEvaluationStorage() { * Creates the evaluate storage using the given image name and environment * variables. * - * @param evalStorageImageName - * name of the evaluation storage image - * @param envVariables - * environment variables that should be given to the component + * @param evalStorageImageName name of the evaluation storage image + * @param envVariables environment variables that should be given to the + * component */ protected void createEvaluationStorage(String evalStorageImageName, String[] envVariables) { evalStoreContainerId = createContainer(evalStorageImageName, Constants.CONTAINER_TYPE_DATABASE, envVariables); @@ -353,13 +346,12 @@ protected void waitForTaskGenToFinish() { } /** - * This method waits for the benchmarked system to terminate or times out - * after the given amount of time (in milliseconds). + * This method waits for the benchmarked system to terminate or times out after + * the given amount of time (in milliseconds). * - * @param maxWaitingTime - * maximum waiting time in milliseconds - * @return {@code true} if the system has been terminated or {@code false} - * if the method timed out + * @param maxWaitingTime maximum waiting time in milliseconds + * @return {@code true} if the system has been terminated or {@code false} if + * the method timed out */ protected boolean waitForSystemToFinish(long maxWaitingTime) { LOGGER.debug("Waiting for the benchmarked system to finish."); @@ -422,8 +414,7 @@ protected void waitForEvalComponentsToFinish() { * Uses the given model as result model if the result model is * null. Else, the two models are merged. * - * @param resultModel - * the new result model + * @param resultModel the new result model */ protected void setResultModel(Model resultModel) { try { @@ -445,9 +436,9 @@ protected void setResultModel(Model resultModel) { /** * Generates a default model containing an error code and the benchmark - * parameters if no result model has been received from the evaluation - * module until now. If the model already has been received, the error is - * added to the existing model. + * parameters if no result model has been received from the evaluation module + * until now. If the model already has been received, the error is added to the + * existing model. */ protected void generateErrorResultModel() { try { @@ -469,8 +460,7 @@ protected void generateErrorResultModel() { } /** - * Adds the {@link #benchmarkParamModel} triples to the {@link #resultModel} - * . + * Adds the {@link #benchmarkParamModel} triples to the {@link #resultModel} . */ protected void addParametersToResultModel() { try { @@ -480,8 +470,7 @@ protected void addParametersToResultModel() { } try { Resource experimentResource = resultModel.getResource(experimentUri); - StmtIterator iterator = benchmarkParamModel.listStatements( - HobbitExperiments.New, null, (RDFNode) null); + StmtIterator iterator = benchmarkParamModel.listStatements(HobbitExperiments.New, null, (RDFNode) null); Statement statement; while (iterator.hasNext()) { statement = iterator.next(); @@ -495,8 +484,7 @@ protected void addParametersToResultModel() { /** * Sends the result RDF model to the platform controller. * - * @param model - * model containing the results + * @param model model containing the results */ protected void sendResultModel(Model model) { try { @@ -558,14 +546,12 @@ public void receiveCommand(byte command, byte[] data) { } /** - * This method handles messages from the command bus containing the - * information that a container terminated. It checks whether the container - * belongs to the current benchmark and whether it has to react. + * This method handles messages from the command bus containing the information + * that a container terminated. It checks whether the container belongs to the + * current benchmark and whether it has to react. * - * @param containerName - * the name of the terminated container - * @param exitCode - * the exit code of the terminated container + * @param containerName the name of the terminated container + * @param exitCode the exit code of the terminated container */ protected void containerTerminated(String containerName, int exitCode) { if (dataGenContainerIds.contains(containerName)) { diff --git a/src/main/java/org/hobbit/core/components/AbstractCommandReceivingComponent.java b/src/main/java/org/hobbit/core/components/AbstractCommandReceivingComponent.java index 5b081c6..b1b5cec 100644 --- a/src/main/java/org/hobbit/core/components/AbstractCommandReceivingComponent.java +++ b/src/main/java/org/hobbit/core/components/AbstractCommandReceivingComponent.java @@ -36,8 +36,11 @@ import org.apache.commons.io.IOUtils; import org.hobbit.core.Commands; import org.hobbit.core.Constants; +import org.hobbit.core.data.ErrorData; +import org.hobbit.core.data.ReportedException; import org.hobbit.core.data.StartCommandData; import org.hobbit.core.data.StopCommandData; +import org.hobbit.core.rabbit.GsonUtils; import org.hobbit.core.rabbit.RabbitMQUtils; import org.hobbit.core.rabbit.RabbitQueueFactory; import org.hobbit.core.rabbit.RabbitQueueFactoryImpl; @@ -69,20 +72,20 @@ public abstract class AbstractCommandReceivingComponent extends AbstractComponen */ private String responseQueueName = null; /** - * Mapping of RabbitMQ's correlationIDs to Future objects corresponding - * to that RPC call. + * Mapping of RabbitMQ's correlationIDs to Future objects corresponding to that + * RPC call. */ private Map> responseFutures = Collections.synchronizedMap(new LinkedHashMap<>()); /** - * Consumer of the queue that is used to receive responses for messages that - * are sent via the command queue and for which an answer is expected. + * Consumer of the queue that is used to receive responses for messages that are + * sent via the command queue and for which an answer is expected. */ private Consumer responseConsumer = null; /** - * Factory for generating queues with which the commands are sent and - * received. It is separated from the data connections since otherwise the - * component can get stuck waiting for a command while the connection is - * busy handling incoming or outgoing data. + * Factory for generating queues with which the commands are sent and received. + * It is separated from the data connections since otherwise the component can + * get stuck waiting for a command while the connection is busy handling + * incoming or outgoing data. */ protected RabbitQueueFactory cmdQueueFactory; /** @@ -107,8 +110,15 @@ public abstract class AbstractCommandReceivingComponent extends AbstractComponen protected long cmdResponseTimeout = DEFAULT_CMD_RESPONSE_TIMEOUT; private ExecutorService cmdThreadPool; - + private boolean errorLogged = false; + /** + * Flag that is used to control whether the + * {@link #reportUnhandledExceptionSavely(Exception)} method should report + * exceptions or not. It can be set to false to provide better, more detailed + * reports while skipping the general reporting of unhandled exceptions. + */ + protected boolean reportUnhandledExceptions = true; /** * Constructor. @@ -120,7 +130,8 @@ public AbstractCommandReceivingComponent() { /** * Constructor. * - * @param execCommandsInParallel flag allowing the processing of commands in parallel + * @param execCommandsInParallel flag allowing the processing of commands in + * parallel */ public AbstractCommandReceivingComponent(boolean execCommandsInParallel) { if (execCommandsInParallel) { @@ -153,7 +164,7 @@ public void run() { try { handleCmd(body, properties); } catch (Exception e) { - if(errorLogged) { + if (errorLogged) { LOGGER.error("Exception while trying to handle incoming command. {}", e.getMessage()); } else { LOGGER.error("Exception while trying to handle incoming command.", e); @@ -175,42 +186,32 @@ public void run() { /** * Sends the given command to the command queue. * - * @param command - * the command that should be sent - * @throws IOException - * if a communication problem occurs + * @param command the command that should be sent + * @throws IOException if a communication problem occurs */ protected void sendToCmdQueue(byte command) throws IOException { sendToCmdQueue(command, null); } /** - * Sends the given command to the command queue with the given data - * appended. + * Sends the given command to the command queue with the given data appended. * - * @param command - * the command that should be sent - * @param data - * data that should be appended to the command - * @throws IOException - * if a communication problem occurs + * @param command the command that should be sent + * @param data data that should be appended to the command + * @throws IOException if a communication problem occurs */ protected void sendToCmdQueue(byte command, byte data[]) throws IOException { sendToCmdQueue(command, data, null); } /** - * Sends the given command to the command queue with the given data appended - * and using the given properties. + * Sends the given command to the command queue with the given data appended and + * using the given properties. * - * @param command - * the command that should be sent - * @param data - * data that should be appended to the command - * @param props - * properties that should be used for the message - * @throws IOException - * if a communication problem occurs + * @param command the command that should be sent + * @param data data that should be appended to the command + * @param props properties that should be used for the message + * @throws IOException if a communication problem occurs */ protected void sendToCmdQueue(byte command, byte data[], BasicProperties props) throws IOException { byte sessionIdBytes[] = getHobbitSessionId().getBytes(Charsets.UTF_8); @@ -232,37 +233,29 @@ protected void sendToCmdQueue(byte command, byte data[], BasicProperties props) } /** - * Adds the given session id to the set of ids this component is reacting - * to. + * Adds the given session id to the set of ids this component is reacting to. * - * @param sessionId - * session id that should be added to the set of accepted ids. + * @param sessionId session id that should be added to the set of accepted ids. */ protected void addCommandHeaderId(String sessionId) { acceptedCmdHeaderIds.add(sessionId); } /** - * This method is called if a message is received - * from the command queue. + * This method is called if a message is received from the command queue. * - * @param bytes - * data from the RabbitMQ message - * @param props - * properties of the RabbitMQ message + * @param bytes data from the RabbitMQ message + * @param props properties of the RabbitMQ message */ protected void handleCmd(byte bytes[], AMQP.BasicProperties props) { handleCmd(bytes, props.getReplyTo()); } /** - * This method is called if a message is received - * from the command queue. + * This method is called if a message is received from the command queue. * - * @param bytes - * data from the RabbitMQ message - * @param replyTo - * name of the queue in which response is expected + * @param bytes data from the RabbitMQ message + * @param replyTo name of the queue in which response is expected */ protected void handleCmd(byte bytes[], String replyTo) { ByteBuffer buffer = ByteBuffer.wrap(bytes); @@ -281,15 +274,13 @@ protected void handleCmd(byte bytes[], String replyTo) { } /** - * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to - * create and start an instance of the given image using the given - * environment variables. + * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to create + * and start an instance of the given image using the given environment + * variables. * - * @param imageName - * the name of the image of the docker container - * @param envVariables - * environment variables that should be added to the created - * container + * @param imageName the name of the image of the docker container + * @param envVariables environment variables that should be added to the created + * container * @return the name of the container instance or null if an error occurred */ protected String createContainer(String imageName, String[] envVariables) { @@ -297,11 +288,10 @@ protected String createContainer(String imageName, String[] envVariables) { } /** - * This method extends (if needed) the array of environment variables - * for the container with HOBBIT specific variables. + * This method extends (if needed) the array of environment variables for the + * container with HOBBIT specific variables. * - * @param envVariables - * user-provided array of environment variables + * @param envVariables user-provided array of environment variables * @return the extended array of environment variables */ protected String[] extendContainerEnvVariables(String[] envVariables) { @@ -322,29 +312,26 @@ protected String[] extendContainerEnvVariables(String[] envVariables) { } /** - * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to - * create and start an instance of the given image using the given - * environment variables. + * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to create + * and start an instance of the given image using the given environment + * variables. * *

* Note that the containerType parameter should have one of the following * values. *

    - *
  • {@link Constants#CONTAINER_TYPE_BENCHMARK} if this container is part - * of a benchmark.
  • - *
  • {@link Constants#CONTAINER_TYPE_DATABASE} if this container is part - * of a benchmark but should be located on a storage node.
  • - *
  • {@link Constants#CONTAINER_TYPE_SYSTEM} if this container is part of - * a benchmarked system.
  • + *
  • {@link Constants#CONTAINER_TYPE_BENCHMARK} if this container is part of a + * benchmark.
  • + *
  • {@link Constants#CONTAINER_TYPE_DATABASE} if this container is part of a + * benchmark but should be located on a storage node.
  • + *
  • {@link Constants#CONTAINER_TYPE_SYSTEM} if this container is part of a + * benchmarked system.
  • *
* - * @param imageName - * the name of the image of the docker container - * @param containerType - * the type of the container - * @param envVariables - * environment variables that should be added to the created - * container + * @param imageName the name of the image of the docker container + * @param containerType the type of the container + * @param envVariables environment variables that should be added to the + * created container * @return the name of the container instance or null if an error occurred */ protected String createContainer(String imageName, String containerType, String[] envVariables) { @@ -352,34 +339,32 @@ protected String createContainer(String imageName, String containerType, String[ } /** - * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to - * create and start an instance of the given image using the given - * environment variables. + * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to create + * and start an instance of the given image using the given environment + * variables. * *

* Note that the containerType parameter should have one of the following * values. *

    - *
  • {@link Constants#CONTAINER_TYPE_BENCHMARK} if this container is part - * of a benchmark.
  • - *
  • {@link Constants#CONTAINER_TYPE_DATABASE} if this container is part - * of a benchmark but should be located on a storage node.
  • - *
  • {@link Constants#CONTAINER_TYPE_SYSTEM} if this container is part of - * a benchmarked system.
  • + *
  • {@link Constants#CONTAINER_TYPE_BENCHMARK} if this container is part of a + * benchmark.
  • + *
  • {@link Constants#CONTAINER_TYPE_DATABASE} if this container is part of a + * benchmark but should be located on a storage node.
  • + *
  • {@link Constants#CONTAINER_TYPE_SYSTEM} if this container is part of a + * benchmarked system.
  • *
* - * @param imageName - * the name of the image of the docker container - * @param containerType - * the type of the container - * @param envVariables - * environment variables that should be added to the created - * container - * @param netAliases - * network aliases that should be added to the created container + * @param imageName the name of the image of the docker container + * @param containerType the type of the container + * @param envVariables environment variables that should be added to the + * created container + * @param netAliases network aliases that should be added to the created + * container * @return the name of the container instance or null if an error occurred */ - protected String createContainer(String imageName, String containerType, String[] envVariables, String[] netAliases) { + protected String createContainer(String imageName, String containerType, String[] envVariables, + String[] netAliases) { try { return createContainerAsync(imageName, containerType, envVariables, netAliases).get(); } catch (ExecutionException | InterruptedException e) { @@ -389,64 +374,61 @@ protected String createContainer(String imageName, String containerType, String[ } /** - * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to - * create and start an instance of the given image using the given - * environment variables. + * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to create + * and start an instance of the given image using the given environment + * variables. * *

* Note that the containerType parameter should have one of the following * values. *

    - *
  • {@link Constants#CONTAINER_TYPE_BENCHMARK} if this container is part - * of a benchmark.
  • - *
  • {@link Constants#CONTAINER_TYPE_DATABASE} if this container is part - * of a benchmark but should be located on a storage node.
  • - *
  • {@link Constants#CONTAINER_TYPE_SYSTEM} if this container is part of - * a benchmarked system.
  • + *
  • {@link Constants#CONTAINER_TYPE_BENCHMARK} if this container is part of a + * benchmark.
  • + *
  • {@link Constants#CONTAINER_TYPE_DATABASE} if this container is part of a + * benchmark but should be located on a storage node.
  • + *
  • {@link Constants#CONTAINER_TYPE_SYSTEM} if this container is part of a + * benchmarked system.
  • *
* - * @param imageName - * the name of the image of the docker container - * @param containerType - * the type of the container - * @param envVariables - * environment variables that should be added to the created - * container - * @return the Future object with the name of the container instance or null if an error occurred + * @param imageName the name of the image of the docker container + * @param containerType the type of the container + * @param envVariables environment variables that should be added to the + * created container + * @return the Future object with the name of the container instance or null if + * an error occurred */ protected Future createContainerAsync(String imageName, String containerType, String[] envVariables) { return createContainerAsync(imageName, containerType, envVariables, null); } /** - * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to - * create and start an instance of the given image using the given - * environment variables. + * This method sends a {@link Commands#DOCKER_CONTAINER_START} command to create + * and start an instance of the given image using the given environment + * variables. * *

* Note that the containerType parameter should have one of the following * values. *

    - *
  • {@link Constants#CONTAINER_TYPE_BENCHMARK} if this container is part - * of a benchmark.
  • - *
  • {@link Constants#CONTAINER_TYPE_DATABASE} if this container is part - * of a benchmark but should be located on a storage node.
  • - *
  • {@link Constants#CONTAINER_TYPE_SYSTEM} if this container is part of - * a benchmarked system.
  • + *
  • {@link Constants#CONTAINER_TYPE_BENCHMARK} if this container is part of a + * benchmark.
  • + *
  • {@link Constants#CONTAINER_TYPE_DATABASE} if this container is part of a + * benchmark but should be located on a storage node.
  • + *
  • {@link Constants#CONTAINER_TYPE_SYSTEM} if this container is part of a + * benchmarked system.
  • *
* - * @param imageName - * the name of the image of the docker container - * @param containerType - * the type of the container - * @param envVariables - * environment variables that should be added to the created - * container - * @param netAliases - * network aliases that should be added to the created container - * @return the Future object with the name of the container instance or null if an error occurred - */ - protected Future createContainerAsync(String imageName, String containerType, String[] envVariables, String[] netAliases) { + * @param imageName the name of the image of the docker container + * @param containerType the type of the container + * @param envVariables environment variables that should be added to the + * created container + * @param netAliases network aliases that should be added to the created + * container + * @return the Future object with the name of the container instance or null if + * an error occurred + */ + protected Future createContainerAsync(String imageName, String containerType, String[] envVariables, + String[] netAliases) { try { envVariables = extendContainerEnvVariables(envVariables); @@ -458,8 +440,8 @@ protected Future createContainerAsync(String imageName, String container responseFutures.put(correlationId, containerFuture); } - byte data[] = RabbitMQUtils.writeString( - gson.toJson(new StartCommandData(imageName, containerType, containerName, envVariables, netAliases))); + byte data[] = GsonUtils.serializeObjectWithGson(gson, + new StartCommandData(imageName, containerType, containerName, envVariables, netAliases)); BasicProperties.Builder propsBuilder = new BasicProperties.Builder(); propsBuilder.deliveryMode(2); propsBuilder.replyTo(responseQueueName); @@ -475,14 +457,14 @@ protected Future createContainerAsync(String imageName, String container } /** - * This method sends a {@link Commands#DOCKER_CONTAINER_STOP} command to - * stop the container with the given id. + * This method sends a {@link Commands#DOCKER_CONTAINER_STOP} command to stop + * the container with the given id. * - * @param containerName - * the name of the container instance that should be stopped + * @param containerName the name of the container instance that should be + * stopped */ protected void stopContainer(String containerName) { - byte data[] = RabbitMQUtils.writeString(gson.toJson(new StopCommandData(containerName))); + byte data[] = GsonUtils.serializeObjectWithGson(gson, new StopCommandData(containerName)); try { sendToCmdQueue(Commands.DOCKER_CONTAINER_STOP, data); } catch (IOException e) { @@ -494,8 +476,7 @@ protected void stopContainer(String containerName) { * Internal method for initializing the {@link #responseQueueName} and the * {@link #responseConsumer} if they haven't been initialized before. * - * @throws IOException - * if a communication problem occurs + * @throws IOException if a communication problem occurs */ private void initResponseQueue() throws IOException { if (responseQueueName == null) { @@ -513,10 +494,12 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp if (key != null) { future = responseFutures.remove(key); if (future == null) { - LOGGER.error("Received a message with correlationId ({}) not in map ({})", key, responseFutures.keySet()); + LOGGER.error("Received a message with correlationId ({}) not in map ({})", key, + responseFutures.keySet()); } } else { - LOGGER.warn("Received a message with null correlationId. This is an error unless the other component uses an older version of HOBBIT core library."); + LOGGER.warn( + "Received a message with null correlationId. This is an error unless the other component uses an older version of HOBBIT core library."); Iterator> iter = responseFutures.values().iterator(); if (iter.hasNext()) { LOGGER.info("Correlating with the eldest request as a workaround."); @@ -553,6 +536,90 @@ public void setCmdResponseTimeout(long cmdResponseTimeout) { this.cmdResponseTimeout = cmdResponseTimeout; } + /** + * This method sends the given error report on the command queue. + * + * @param error the data of the error that should be reported + * @throws IOException if sending the report fails + */ + public void reportError(ErrorData error) throws IOException { + sendToCmdQueue(Commands.REPORT_ERROR, GsonUtils.serializeObjectWithGson(gson, error)); + } + + /** + * This method sends the given error report on the command queue without + * throwing an exception. This can be helpful in situations, in which the + * reported error may lead to a crash of the system and the reporting mechanism + * itself may not work anymore. + * + * @param error the data of the error that should be reported + */ + public void reportErrorSavely(ErrorData error) { + try { + reportError(error); + } catch (IOException e) { + LOGGER.error("Error while reporting an error.", e); + } + } + + /** + * This method sends a report about the given exception. The container name is + * derived from the configuration and the error report is generated based on the + * data provided by the exception. Note that instances of + * {@link ReportedException} are not reported. + * + * @param e the severe exception that should be reported as error + */ + public void reportExceptionSavely(Exception e) { + if (!(e instanceof ReportedException)) { + reportErrorSavely(ErrorData.createFromException(e, + configuration.getString(Constants.CONTAINER_NAME_KEY, (String) null))); + } + } + + /** + * This method sends a report about the given unhandled exception using the + * {@link #reportExceptionSavely(Exception)} method unless the flag + * {@link #reportUnhandledExceptions} is set to {@code false}. + * + * @param e the severe exception that should be reported as error + */ + public void reportUnhandledExceptionSavely(Exception e) { + if (reportUnhandledExceptions) { + reportExceptionSavely(e); + } + } + + /** + * Report the given unhandled exception using + * {@link #reportUnhandledExceptionSavely(Exception)} and return a new instance + * of {@link ReportedException} that can be thrown. + * + * @param e the severe exception that should be reported as error + * @return the new exception instance that can be thrown + */ + public Exception reportAndWrap(Exception e) { + if (e instanceof ReportedException) { + return e; + } + reportExceptionSavely(e); + return new ReportedException(e); + } + + /** + * @return the reportUnhandledExceptions + */ + public boolean isReportUnhandledExceptions() { + return reportUnhandledExceptions; + } + + /** + * @param reportUnhandledExceptions the reportUnhandledExceptions to set + */ + public void setReportUnhandledExceptions(boolean reportUnhandledExceptions) { + this.reportUnhandledExceptions = reportUnhandledExceptions; + } + @Override public void close() throws IOException { if (cmdChannel != null) { diff --git a/src/main/java/org/hobbit/core/components/AbstractDataGenerator.java b/src/main/java/org/hobbit/core/components/AbstractDataGenerator.java index ddee396..feae186 100644 --- a/src/main/java/org/hobbit/core/components/AbstractDataGenerator.java +++ b/src/main/java/org/hobbit/core/components/AbstractDataGenerator.java @@ -36,7 +36,7 @@ public abstract class AbstractDataGenerator extends AbstractPlatformConnectorCom public AbstractDataGenerator() { defaultContainerType = Constants.CONTAINER_TYPE_BENCHMARK; } - + @Override public void init() throws Exception { super.init(); @@ -52,15 +52,19 @@ public void init() throws Exception { @Override public void run() throws Exception { - sendToCmdQueue(Commands.DATA_GENERATOR_READY_SIGNAL); - // Wait for the start message - startDataGenMutex.acquire(); - - generateData(); - - // We have to wait until all messages are consumed - sender2TaskGen.closeWhenFinished(); - sender2System.closeWhenFinished(); + try { + sendToCmdQueue(Commands.DATA_GENERATOR_READY_SIGNAL); + // Wait for the start message + startDataGenMutex.acquire(); + + generateData(); + + // We have to wait until all messages are consumed + sender2TaskGen.closeWhenFinished(); + sender2System.closeWhenFinished(); + } catch (Exception e) { + throw reportAndWrap(e); + } } protected abstract void generateData() throws Exception; diff --git a/src/main/java/org/hobbit/core/components/AbstractEvaluationModule.java b/src/main/java/org/hobbit/core/components/AbstractEvaluationModule.java index 9d1f1fb..092e04b 100644 --- a/src/main/java/org/hobbit/core/components/AbstractEvaluationModule.java +++ b/src/main/java/org/hobbit/core/components/AbstractEvaluationModule.java @@ -66,7 +66,7 @@ public abstract class AbstractEvaluationModule extends AbstractPlatformConnector /** * Timeout parameter for delivery queue message poll. */ - private static final int QUEUEPOLLTIMEOUT=600000; + private static final int QUEUEPOLLTIMEOUT = 600000; public AbstractEvaluationModule() { defaultContainerType = Constants.CONTAINER_TYPE_BENCHMARK; @@ -77,12 +77,12 @@ public void init() throws Exception { super.init(); // Get the experiment URI - experimentUri = configuration.getString(Constants.HOBBIT_EXPERIMENT_URI_KEY,LOGGER); - - evalModule2EvalStoreQueue = getFactoryForOutgoingDataQueues() - .createDefaultRabbitQueue(generateSessionQueueName(Constants.EVAL_MODULE_2_EVAL_STORAGE_DEFAULT_QUEUE_NAME)); - evalStore2EvalModuleQueue = getFactoryForIncomingDataQueues() - .createDefaultRabbitQueue(generateSessionQueueName(Constants.EVAL_STORAGE_2_EVAL_MODULE_DEFAULT_QUEUE_NAME)); + experimentUri = configuration.getString(Constants.HOBBIT_EXPERIMENT_URI_KEY, LOGGER); + + evalModule2EvalStoreQueue = getFactoryForOutgoingDataQueues().createDefaultRabbitQueue( + generateSessionQueueName(Constants.EVAL_MODULE_2_EVAL_STORAGE_DEFAULT_QUEUE_NAME)); + evalStore2EvalModuleQueue = getFactoryForIncomingDataQueues().createDefaultRabbitQueue( + generateSessionQueueName(Constants.EVAL_STORAGE_2_EVAL_MODULE_DEFAULT_QUEUE_NAME)); consumer = new QueueingConsumer(evalStore2EvalModuleQueue.channel); evalStore2EvalModuleQueue.channel.basicConsume(evalStore2EvalModuleQueue.name, consumer); @@ -90,20 +90,23 @@ public void init() throws Exception { @Override public void run() throws Exception { - sendToCmdQueue(Commands.EVAL_MODULE_READY_SIGNAL); - collectResponses(); - Model model = summarizeEvaluation(); - LOGGER.info("The result model has " + model.size() + " triples."); - sendResultModel(model); + try { + sendToCmdQueue(Commands.EVAL_MODULE_READY_SIGNAL); + collectResponses(); + Model model = summarizeEvaluation(); + LOGGER.info("The result model has " + model.size() + " triples."); + sendResultModel(model); + } catch (Exception e) { + throw reportAndWrap(e); + } } /** - * This method communicates with the evaluation storage to collect all - * response pairs. For every pair the + * This method communicates with the evaluation storage to collect all response + * pairs. For every pair the * {@link #evaluateResponse(byte[], byte[], long, long)} method is called. * - * @throws Exception - * if a communication error occurs. + * @throws Exception if a communication error occurs. */ protected void collectResponses() throws Exception { byte[] expectedData; @@ -119,19 +122,16 @@ protected void collectResponses() throws Exception { // request next response pair props = new BasicProperties.Builder().deliveryMode(2).replyTo(evalStore2EvalModuleQueue.name).build(); evalModule2EvalStoreQueue.channel.basicPublish("", evalModule2EvalStoreQueue.name, props, requestBody); - //Wait for delivery message + // Wait for delivery message Delivery delivery = consumer.getDeliveryQueue().poll(QUEUEPOLLTIMEOUT, TimeUnit.MILLISECONDS); // parse the response - if (delivery == null) - { - LOGGER.error("No Message Received after waiting for ten minutes"); - return; + if (delivery == null) { + LOGGER.error("No Message Received after waiting for ten minutes"); + return; } - buffer = ByteBuffer.wrap(delivery.getBody()); - - - + buffer = ByteBuffer.wrap(delivery.getBody()); + // if the response is empty if (buffer.remaining() == 0) { LOGGER.error("Got a completely empty response from the evaluation storage."); @@ -151,26 +151,22 @@ protected void collectResponses() throws Exception { responseReceivedTimestamp = data.length > 0 ? RabbitMQUtils.readLong(data) : 0; receivedData = RabbitMQUtils.readByteArray(buffer); - evaluateResponse(expectedData, receivedData, taskSentTimestamp, responseReceivedTimestamp); - + } } /** * Evaluates the given response pair. * - * @param expectedData - * the data that has been expected - * @param receivedData - * the data that has been received from the system - * @param taskSentTimestamp - * the time at which the task has been sent to the system - * @param responseReceivedTimestamp - * the time at which the response has been received from the - * system - * @throws Exception - * if an error occurs during the evaluation + * @param expectedData the data that has been expected + * @param receivedData the data that has been received from the + * system + * @param taskSentTimestamp the time at which the task has been sent to + * the system + * @param responseReceivedTimestamp the time at which the response has been + * received from the system + * @throws Exception if an error occurs during the evaluation */ protected abstract void evaluateResponse(byte[] expectedData, byte[] receivedData, long taskSentTimestamp, long responseReceivedTimestamp) throws Exception; @@ -180,18 +176,15 @@ protected abstract void evaluateResponse(byte[] expectedData, byte[] receivedDat * evaluation results. * * @return an RDF model containing the evaluation results - * @throws Exception - * if a sever error occurs + * @throws Exception if a sever error occurs */ protected abstract Model summarizeEvaluation() throws Exception; /** * Sends the model to the benchmark controller. * - * @param model - * the model that should be sent - * @throws IOException - * if an error occurs during the commmunication + * @param model the model that should be sent + * @throws IOException if an error occurs during the commmunication */ private void sendResultModel(Model model) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); diff --git a/src/main/java/org/hobbit/core/components/AbstractSystemAdapter.java b/src/main/java/org/hobbit/core/components/AbstractSystemAdapter.java index 1b0d69e..860ccd9 100644 --- a/src/main/java/org/hobbit/core/components/AbstractSystemAdapter.java +++ b/src/main/java/org/hobbit/core/components/AbstractSystemAdapter.java @@ -98,9 +98,9 @@ public AbstractSystemAdapter() { /** * Constructor setting the maximum number of messages processed in parallel. * - * @param maxParallelProcessedMsgs - * The maximum number of incoming messages of a single queue that are - * processed in parallel. Additional messages have to wait. + * @param maxParallelProcessedMsgs The maximum number of incoming messages of a + * single queue that are processed in parallel. + * Additional messages have to wait. */ public AbstractSystemAdapter(int maxParallelProcessedMsgs) { this.maxParallelProcessedMsgs = maxParallelProcessedMsgs; @@ -113,7 +113,7 @@ public void init() throws Exception { // Get the benchmark parameter model systemParamModel = configuration.getModel(Constants.SYSTEM_PARAMETERS_MODEL_KEY, - () -> ModelFactory.createDefaultModel(), LOGGER); + () -> ModelFactory.createDefaultModel(), LOGGER); dataGenReceiver = DataReceiverImpl.builder().maxParallelProcessedMsgs(maxParallelProcessedMsgs) .queue(incomingDataQueueFactory, generateSessionQueueName(Constants.DATA_GEN_2_SYSTEM_QUEUE_NAME)) @@ -142,22 +142,26 @@ public void handleData(byte[] data) { @Override public void run() throws Exception { - sendToCmdQueue(Commands.SYSTEM_READY_SIGNAL); - - terminateMutex.acquire(); - // Check whether the system should abort try { - causeMutex.acquire(); - if (cause != null) { - throw cause; + sendToCmdQueue(Commands.SYSTEM_READY_SIGNAL); + + terminateMutex.acquire(); + // Check whether the system should abort + try { + causeMutex.acquire(); + if (cause != null) { + throw cause; + } + causeMutex.release(); + } catch (InterruptedException e) { + LOGGER.error("Interrupted while waiting to set the termination cause."); } - causeMutex.release(); - } catch (InterruptedException e) { - LOGGER.error("Interrupted while waiting to set the termination cause."); + // Close receivers as soon as all messages have been received + dataGenReceiver.closeWhenFinished(); + taskGenReceiver.closeWhenFinished(); + } catch (Exception e) { + throw reportAndWrap(e); } - // Close receivers as soon as all messages have been received - dataGenReceiver.closeWhenFinished(); - taskGenReceiver.closeWhenFinished(); } @Override @@ -173,12 +177,9 @@ public void receiveCommand(byte command, byte[] data) { * This method sends the given result data for the task with the given task id * to the evaluation storage. * - * @param taskIdString - * the id of the task - * @param data - * the data of the task - * @throws IOException - * if there is an error during the sending + * @param taskIdString the id of the task + * @param data the data of the task + * @throws IOException if there is an error during the sending */ protected void sendResultToEvalStorage(String taskIdString, byte[] data) throws IOException { byte[] taskIdBytes = taskIdString.getBytes(Charsets.UTF_8); @@ -198,9 +199,8 @@ protected void sendResultToEvalStorage(String taskIdString, byte[] data) throws * given, it will be thrown causing an abortion from the main thread instead of * a normal termination. * - * @param cause - * the cause for an abortion of the process or {code null} if the - * component should terminate in a normal way. + * @param cause the cause for an abortion of the process or {code null} if the + * component should terminate in a normal way. */ protected synchronized void terminate(Exception cause) { if (cause != null) { diff --git a/src/main/java/org/hobbit/core/components/AbstractTaskGenerator.java b/src/main/java/org/hobbit/core/components/AbstractTaskGenerator.java index e614401..0c20b0a 100644 --- a/src/main/java/org/hobbit/core/components/AbstractTaskGenerator.java +++ b/src/main/java/org/hobbit/core/components/AbstractTaskGenerator.java @@ -32,7 +32,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** * This abstract class implements basic functions that can be used to implement * a task generator. @@ -110,21 +109,21 @@ public AbstractTaskGenerator() { * maxParallelProcessedMsgs=1 leads to the usage of a * {@link QueueingConsumer}. * - * @param maxParallelProcessedMsgs - * the number of messaegs that are processed in parallel + * @param maxParallelProcessedMsgs the number of messaegs that are processed in + * parallel */ public AbstractTaskGenerator(int maxParallelProcessedMsgs) { this.maxParallelProcessedMsgs = maxParallelProcessedMsgs; defaultContainerType = Constants.CONTAINER_TYPE_BENCHMARK; } - @Override + @Override public void init() throws Exception { super.init(); - generatorId = configuration.getInt(Constants.GENERATOR_ID_KEY,LOGGER); + generatorId = configuration.getInt(Constants.GENERATOR_ID_KEY, LOGGER); nextTaskId = generatorId; - numberOfGenerators = configuration.getInt(Constants.GENERATOR_COUNT_KEY,LOGGER); + numberOfGenerators = configuration.getInt(Constants.GENERATOR_COUNT_KEY, LOGGER); sender2System = DataSenderImpl.builder().queue(getFactoryForOutgoingDataQueues(), generateSessionQueueName(Constants.TASK_GEN_2_SYSTEM_QUEUE_NAME)).build(); @@ -142,19 +141,22 @@ public void handleData(byte[] data) { @Override public void run() throws Exception { - sendToCmdQueue(Commands.TASK_GENERATOR_READY_SIGNAL); - // Wait for the start message - startTaskGenMutex.acquire(); - currentlyProcessedMessages.release(maxParallelProcessedMsgs); - // Wait for message to terminate - - terminateMutex.acquire(); - dataGenReceiver.closeWhenFinished(); - // make sure that all messages have been delivered (otherwise they might - // be lost) - sender2System.closeWhenFinished(); - sender2EvalStore.closeWhenFinished(); - + try { + sendToCmdQueue(Commands.TASK_GENERATOR_READY_SIGNAL); + // Wait for the start message + startTaskGenMutex.acquire(); + currentlyProcessedMessages.release(maxParallelProcessedMsgs); + // Wait for message to terminate + + terminateMutex.acquire(); + dataGenReceiver.closeWhenFinished(); + // make sure that all messages have been delivered (otherwise they might + // be lost) + sender2System.closeWhenFinished(); + sender2EvalStore.closeWhenFinished(); + } catch (Exception e) { + throw reportAndWrap(e); + } } @Override @@ -164,9 +166,7 @@ public void receiveGeneratedData(byte[] data) { generateTask(data); } catch (Exception e) { LOGGER.error("Exception while generating task.", e); - } - finally - { + } finally { currentlyProcessedMessages.release(1); } } @@ -176,10 +176,8 @@ public void receiveGeneratedData(byte[] data) { * timestamp of the moment at which the message has been sent to the system and * sends it together with the expected response to the evaluation storage. * - * @param data - * incoming data generated by a data generator - * @throws Exception - * if a sever error occurred + * @param data incoming data generated by a data generator + * @throws Exception if a sever error occurred */ protected abstract void generateTask(byte[] data) throws Exception; @@ -212,15 +210,11 @@ public void receiveCommand(byte command, byte[] data) { * This method sends the given data and the given timestamp of the task with the * given task id to the evaluation storage. * - * @param taskIdString - * the id of the task - * @param timestamp - * the timestamp of the moment in which the task has been sent to the - * system - * @param data - * the expected response for the task with the given id - * @throws IOException - * if there is an error during the sending + * @param taskIdString the id of the task + * @param timestamp the timestamp of the moment in which the task has been + * sent to the system + * @param data the expected response for the task with the given id + * @throws IOException if there is an error during the sending */ protected void sendTaskToEvalStorage(String taskIdString, long timestamp, byte[] data) throws IOException { sender2EvalStore.sendData(RabbitMQUtils.writeByteArrays(null, @@ -230,12 +224,9 @@ protected void sendTaskToEvalStorage(String taskIdString, long timestamp, byte[] /** * Sends the given task with the given task id and data to the system. * - * @param taskIdString - * the id of the task - * @param data - * the data of the task - * @throws IOException - * if there is an error during the sending + * @param taskIdString the id of the task + * @param data the data of the task + * @throws IOException if there is an error during the sending */ protected void sendTaskToSystemAdapter(String taskIdString, byte[] data) throws IOException { sender2System.sendData( diff --git a/src/main/java/org/hobbit/core/data/ErrorData.java b/src/main/java/org/hobbit/core/data/ErrorData.java index 7c5b5bd..9a1dd85 100644 --- a/src/main/java/org/hobbit/core/data/ErrorData.java +++ b/src/main/java/org/hobbit/core/data/ErrorData.java @@ -1,5 +1,10 @@ package org.hobbit.core.data; +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.hobbit.vocab.HobbitErrors; + /** * A simple data structure that represents errors that components can report. * @@ -11,7 +16,7 @@ public class ErrorData { /** * ID of the container reporting the error. */ - protected String containerId; + protected String containerName; /** * IRI of the error type (optional). */ @@ -36,14 +41,14 @@ public class ErrorData { * @return the containerId */ public String getContainerId() { - return containerId; + return containerName; } /** * @param containerId the containerId to set */ public void setContainerId(String containerId) { - this.containerId = containerId; + this.containerName = containerId; } /** @@ -102,4 +107,82 @@ public void setDetails(String details) { this.details = details; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((containerName == null) ? 0 : containerName.hashCode()); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + ((details == null) ? 0 : details.hashCode()); + result = prime * result + ((errorType == null) ? 0 : errorType.hashCode()); + result = prime * result + ((label == null) ? 0 : label.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ErrorData other = (ErrorData) obj; + if (containerName == null) { + if (other.containerName != null) + return false; + } else if (!containerName.equals(other.containerName)) + return false; + if (description == null) { + if (other.description != null) + return false; + } else if (!description.equals(other.description)) + return false; + if (details == null) { + if (other.details != null) + return false; + } else if (!details.equals(other.details)) + return false; + if (errorType == null) { + if (other.errorType != null) + return false; + } else if (!errorType.equals(other.errorType)) + return false; + if (label == null) { + if (other.label != null) + return false; + } else if (!label.equals(other.label)) + return false; + return true; + } + + /** + * Creates an instance of a {@link HobbitErrors#UnhandledException} error based + * on the data of the given exception. + * + * @param e the exception that should be expressed as error + * @param containerName the ID of the container that reports the error + * @return the newly created ErrorData instance or {@code null} if the given + * container name is {@code null}. + */ + public static ErrorData createFromException(Exception e, String containerName) { + if (containerName == null) { + return null; + } + ErrorData result = new ErrorData(); + result.containerName = containerName; + result.errorType = HobbitErrors.UnhandledException.getURI(); + result.label = e.getClass().getName(); + result.description = e.getMessage(); + // Get the full stack trace + StringWriter writer = new StringWriter(); + PrintWriter pwriter = new PrintWriter(writer); + e.printStackTrace(pwriter); + pwriter.flush(); + result.details = writer.toString(); + pwriter.close(); + + return result; + } + } diff --git a/src/main/java/org/hobbit/core/data/ReportedException.java b/src/main/java/org/hobbit/core/data/ReportedException.java new file mode 100644 index 0000000..85173c7 --- /dev/null +++ b/src/main/java/org/hobbit/core/data/ReportedException.java @@ -0,0 +1,26 @@ +package org.hobbit.core.data; + +/** + * This Exception is a wrapper of an exception that has already been reported on + * the command queue and, hence, doesn't have to be reported again. + * + * @author Michael Röder (michael.roeder@uni-paderborn.de) + * + */ +public class ReportedException extends Exception { + + private static final long serialVersionUID = 2L; + + public ReportedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public ReportedException(String message, Throwable cause) { + super(message, cause); + } + + public ReportedException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/org/hobbit/core/run/ComponentStarter.java b/src/main/java/org/hobbit/core/run/ComponentStarter.java index 2b87ea5..9d9de67 100644 --- a/src/main/java/org/hobbit/core/run/ComponentStarter.java +++ b/src/main/java/org/hobbit/core/run/ComponentStarter.java @@ -23,6 +23,7 @@ import org.apache.commons.configuration2.EnvironmentConfiguration; import org.apache.commons.io.IOUtils; import org.hobbit.core.Constants; +import org.hobbit.core.components.AbstractCommandReceivingComponent; import org.hobbit.core.components.Component; import org.hobbit.utils.config.HobbitConfiguration; import org.slf4j.Logger; @@ -66,6 +67,7 @@ public static void main(String[] args) { component.run(); } catch (Throwable t) { LOGGER.error("Exception while executing component. Exiting with error code.", t); + reportErrorIfPossible(t); success = false; } finally { closeComponent(); @@ -122,7 +124,7 @@ private static void configure(Component component) { HobbitConfiguration configuration = new HobbitConfiguration(); configuration.addConfiguration(new EnvironmentConfiguration()); // Add more configurations if necessary - + component.setConfiguration(configuration); } @@ -154,4 +156,11 @@ private static boolean forceTermination() { } return false; } + + private static void reportErrorIfPossible(Throwable t) { + if (component instanceof AbstractCommandReceivingComponent && t instanceof Exception) { + AbstractCommandReceivingComponent crComponent = (AbstractCommandReceivingComponent) component; + crComponent.reportUnhandledExceptionSavely((Exception) t); + } + } } diff --git a/src/main/java/org/hobbit/vocab/HobbitErrors.java b/src/main/java/org/hobbit/vocab/HobbitErrors.java index bb32d1b..d014cd7 100644 --- a/src/main/java/org/hobbit/vocab/HobbitErrors.java +++ b/src/main/java/org/hobbit/vocab/HobbitErrors.java @@ -57,4 +57,6 @@ protected static final Property property(String local) { public static final Resource TerminatedByUser = resource("TerminatedByUser"); public static final Resource UnexpectedError = resource("UnexpectedError"); public static final Resource ClusterNotHealthy = resource("ClusterNotHealthy"); + public static final Resource UnhandledException = resource("UnhandledException"); + public static final Resource UnspecifiedError = resource("UnspecifiedError"); }