diff options
author | Dirk Fauth | 2020-11-19 13:16:45 +0000 |
---|---|---|
committer | Dirk Fauth | 2020-11-19 13:16:45 +0000 |
commit | a4ace7cc30573b7d6be9c4a695eff8a3767050fb (patch) | |
tree | 499d0b44eaa4bb1d6bac006829a9b932c93b1247 | |
parent | 595bf13b637f7d6baf1fb71010f306464f6cf421 (diff) | |
download | org.eclipse.app4mc.cloud-a4ace7cc30573b7d6be9c4a695eff8a3767050fb.tar.gz org.eclipse.app4mc.cloud-a4ace7cc30573b7d6be9c4a695eff8a3767050fb.tar.xz org.eclipse.app4mc.cloud-a4ace7cc30573b7d6be9c4a695eff8a3767050fb.zip |
Added preparation for tree based service execution
Change-Id: I0112de33aa1458ffdd33ea9b755bffb30019671f
Signed-off-by: Dirk Fauth <Dirk.Fauth@de.bosch.com>
11 files changed, 929 insertions, 212 deletions
diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/CloudServiceExecutionRunnable.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/CloudServiceExecutionRunnable.java index 1bd7830..333c0a0 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/CloudServiceExecutionRunnable.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/CloudServiceExecutionRunnable.java @@ -66,10 +66,10 @@ public class CloudServiceExecutionRunnable implements Runnable { public void run() { try { Path inputFile = this.storageService.load(this.uuid, this.originalFilename); - for (CloudServiceDefinition service : this.workflowStatus.getSelectedServices()) { - if (StringUtils.isEmpty(service)) { - continue; - } + + // TODO implement tree based processing with nested threads + + for (ServiceNode node : this.workflowStatus.getSelectedServices()) { // if an error occurred stop the workflow if (!this.workflowStatus.getErrors().isEmpty()) { @@ -77,7 +77,7 @@ public class CloudServiceExecutionRunnable implements Runnable { } try { - inputFile = executeCloudService(service, this.workflowStatus, inputFile); + inputFile = executeCloudService(node, this.workflowStatus, inputFile); } catch (ProcessingFailedException e) { this.workflowStatus.addError(e.getMessage()); } @@ -102,8 +102,10 @@ public class CloudServiceExecutionRunnable implements Runnable { } } - private Path executeCloudService(CloudServiceDefinition csd, WorkflowStatus workflowStatus, Path inputFile) { + private Path executeCloudService(ServiceNode node, WorkflowStatus workflowStatus, Path inputFile) { try { + CloudServiceDefinition csd = node.getService(); + ServiceConfiguration config = node.getServiceConfiguration(); String serviceKey = csd.getKey(); String serviceName = csd.getName(); String baseUrl = csd.getBaseUrl(); @@ -112,7 +114,6 @@ public class CloudServiceExecutionRunnable implements Runnable { MultipartBody multipartBody = Unirest.post(baseUrl) .field("file", Files.newInputStream(inputFile), inputFile.getFileName().toString()); - ServiceConfiguration config = workflowStatus.getConfiguration(serviceKey); if (config != null) { config.getParameterList().forEach(param -> { if (!StringUtils.isEmpty(param.getValue()) && !param.isManagerParameter()) { @@ -168,9 +169,8 @@ public class CloudServiceExecutionRunnable implements Runnable { long end = System.currentTimeMillis(); long timeout = 60_000l; - ServiceConfiguration serviceConfiguration = workflowStatus.getConfiguration(serviceKey); - if (serviceConfiguration != null) { - ServiceConfigurationParameter timeoutParam = serviceConfiguration.getParameter("timeout"); + if (config != null) { + ServiceConfigurationParameter timeoutParam = config.getParameter("timeout"); if (timeoutParam != null) { try { timeout = Long.valueOf(timeoutParam.getValue()); @@ -294,8 +294,8 @@ public class CloudServiceExecutionRunnable implements Runnable { } boolean deleteResult = true; - if (serviceConfiguration != null) { - ServiceConfigurationParameter deleteParam = serviceConfiguration.getParameter("deleteResult"); + if (config != null) { + ServiceConfigurationParameter deleteParam = config.getParameter("deleteResult"); if (deleteParam != null) { deleteResult = Boolean.valueOf(deleteParam.getValue()); } @@ -316,7 +316,7 @@ public class CloudServiceExecutionRunnable implements Runnable { return result; } catch (Exception e) { - throw new ProcessingFailedException("Error in " + csd.getName() + " workflow: " + e.getMessage(), e); + throw new ProcessingFailedException("Error in " + node.getService().getName() + " workflow: " + e.getMessage(), e); } } diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNode.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNode.java new file mode 100644 index 0000000..8951344 --- /dev/null +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/ServiceNode.java @@ -0,0 +1,178 @@ +/********************************************************************************* + * Copyright (c) 2020 Robert Bosch GmbH and others. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Robert Bosch GmbH - initial API and implementation + ******************************************************************************** + */ +package org.eclipse.app4mc.cloud.manager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition; + +public class ServiceNode { + + private final String id; + private final CloudServiceDefinition service; + private final ServiceConfiguration config; + + private final List<ServiceNode> children = new ArrayList<>(); + + /** + * Create a structural node like for example the root node. + */ + public ServiceNode(String id) { + if (id == null) { + throw new IllegalArgumentException("id can not be null!"); + } + + this.id = id; + this.service = null; + this.config = null; + } + + /** + * Create a service node for the given {@link CloudServiceDefinition} and + * corresponding {@link ServiceConfiguration}. + * + * @param service The {@link CloudServiceDefinition} to store in this node. + * @param config The {@link ServiceConfiguration} for the service. + */ + public ServiceNode(CloudServiceDefinition service, ServiceConfiguration config) { + if (service == null) { + throw new IllegalArgumentException("service parameter can not be null!"); + } + this.id = service.getKey(); + this.service = service; + this.config = config; + } + + /** + * + * @return The id of this node. A custom string for a structural node, the key + * of the {@link CloudServiceDefinition} for a service node. + */ + public String getId() { + return this.id; + } + + /** + * + * @return The {@link CloudServiceDefinition} stored in this node. + */ + public CloudServiceDefinition getService() { + return service; + } + + /** + * + * @return The {@link ServiceConfiguration} for the + * {@link CloudServiceDefinition} stored in this node. Can be + * <code>null</code>. + */ + public ServiceConfiguration getServiceConfiguration() { + return config; + } + + /** + * Adds the given {@link ServiceNode} to the local children list. + * + * @param node The {@link ServiceNode} to add. + */ + public void addChild(ServiceNode node) { + this.children.add(node); + } + + /** + * Adds a new child that references the given {@link CloudServiceDefinition} + * with its corresponding {@link ServiceConfiguration}. + * + * @param service The {@link CloudServiceDefinition} that should be added as a + * child node. Can not be <code>null</code>. + * @param config The corresponding {@link ServiceConfiguration} that provides + * configurations for the {@link CloudServiceDefinition}. Can be + * <code>null</code>. + */ + public void addChild(CloudServiceDefinition service, ServiceConfiguration config) { + this.children.add(new ServiceNode(service, config)); + } + + /** + * Removes the given {@link ServiceNode} from the local children list. + * + * @param node The {@link ServiceNode} to remove. + */ + public void removeChild(ServiceNode node) { + this.children.remove(node); + } + + /** + * Removes the given {@link ServiceNode} with the given id from the local + * children list. + * + * @param id The id of the {@link ServiceNode} to remove. + */ + public void removeChild(String id) { + for (Iterator<ServiceNode> it = this.children.iterator(); it.hasNext(); ) { + ServiceNode child = it.next(); + if (child.getId().equals(id)) { + it.remove(); + } + } + } + + /** + * Removes the {@link ServiceNode} from the local children list that references + * the given {@link CloudServiceDefinition}. + * + * @param toRemove The {@link CloudServiceDefinition} for which the + * {@link ServiceNode} should be removed. + */ + public void removeChild(CloudServiceDefinition toRemove) { + for (Iterator<ServiceNode> it = this.children.iterator(); it.hasNext(); ) { + ServiceNode child = it.next(); + if (child.getService().getKey().equals(toRemove.getKey())) { + it.remove(); + } + } + } + + /** + * + * @return An unmodifiable list of the child {@link ServiceNode}s in this node. + */ + public List<ServiceNode> getChildren() { + return Collections.unmodifiableList(this.children); + } + + /** + * Clear the locally mapped services in this node. + */ + public void clearChildren() { + this.children.clear(); + } + + /** + * + * @return <code>true</code> if this node is only used for structuring (e.g. a + * root node), <code>false</code> if it references a service definition. + */ + public boolean isStructuralNode() { + return this.service == null; + } + + @Override + public String toString() { + return this.id; + } +} diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowConfigController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowConfigController.java index 81ced37..a85fb56 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowConfigController.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowConfigController.java @@ -113,7 +113,7 @@ public class WorkflowConfigController { this.cloudServiceDefinitions); // add default service configurations if not specified - addDefaultConfigurations(workflowStatus); + WorkflowStatusHelper.addDefaultConfigurations(workflowStatus); if (workflowStatus == null) { redirectAttributes.addFlashAttribute( @@ -151,42 +151,6 @@ public class WorkflowConfigController { return "redirect:/workflow?uuid=" + uuid; } - private void addDefaultConfigurations(WorkflowStatus workflowStatus) { - for (CloudServiceDefinition csd : workflowStatus.getSelectedServices()) { - // check if a configuration is already available - ServiceConfiguration configuration = workflowStatus.getConfiguration(csd.getKey()); - if (configuration == null) { - configuration = new ServiceConfiguration(csd.getName()); - workflowStatus.addConfiguration(csd.getKey(), configuration); - } - - ServiceConfigurationParameter timeOut = configuration.getParameter("timeout"); - if (timeOut == null) { - timeOut = new ServiceConfigurationParameter(); - timeOut.setName("Timeout (in ms)"); - timeOut.setKey("timeout"); - timeOut.setValue("60000"); - if ("app4mc_sim".equals(csd.getKey())) { - timeOut.setValue("-1"); - } - timeOut.setType("Long"); - timeOut.setManagerParameter(true); - configuration.addParameter(timeOut); - } - - ServiceConfigurationParameter deleteResult = configuration.getParameter("deleteResult"); - if (deleteResult == null) { - deleteResult = new ServiceConfigurationParameter(); - deleteResult.setName("Delete result "); - deleteResult.setKey("deleteResult"); - deleteResult.setValue("true"); - deleteResult.setType("boolean"); - deleteResult.setManagerParameter(true); - configuration.addParameter(deleteResult); - } - } - } - @ModelAttribute("exampleModelFiles") public List<String> exampleModelFiles() { return this.exampleModelFiles; diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java index 23d4e6a..de19c13 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowController.java @@ -209,6 +209,8 @@ public class WorkflowController { return "workflow"; } + // TODO extend selection and removal of services to handle parents + @PostMapping("/select/{selected}") public String selectService( @PathVariable(name = "selected") String selected, @@ -223,8 +225,7 @@ public class WorkflowController { return "workflow"; } - ws.addSelectedService(csd); - ws.addConfiguration(csd.getKey(), WorkflowStatusHelper.getConfigurationForService(csd)); + ws.addSelectedService(csd, WorkflowStatusHelper.getConfigurationForService(csd)); // render the form view return "workflow"; @@ -242,7 +243,6 @@ public class WorkflowController { if (csd != null) { ws.removeSelectedService(csd); - ws.removeConfiguration(selected); } // render the form view @@ -331,11 +331,10 @@ public class WorkflowController { return ResponseEntity.notFound().build(); } - // create archive of results + // create workflow configuration json try { WorkflowStatus config = new WorkflowStatus(); - existing.getSelectedServices().forEach(config::addSelectedService); - existing.getConfigurations().entrySet().forEach(entry -> config.addConfiguration(entry.getKey(), entry.getValue())); + config.addAllSelectedServices(existing.getSelectedServices()); return ResponseEntity .ok() diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowRestController.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowRestController.java index 6d95103..440ffa7 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowRestController.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowRestController.java @@ -97,7 +97,7 @@ public class WorkflowRestController { this.cloudServiceDefinitions); // add default service configurations if not specified - addDefaultConfigurations(workflowStatus); + WorkflowStatusHelper.addDefaultConfigurations(workflowStatus); if (workflowStatus == null) { return ResponseEntity @@ -246,42 +246,6 @@ public class WorkflowRestController { return ResponseEntity.ok().build(); } - private void addDefaultConfigurations(WorkflowStatus workflowStatus) { - for (CloudServiceDefinition csd : workflowStatus.getSelectedServices()) { - // check if a configuration is already available - ServiceConfiguration configuration = workflowStatus.getConfiguration(csd.getKey()); - if (configuration == null) { - configuration = new ServiceConfiguration(csd.getName()); - workflowStatus.addConfiguration(csd.getKey(), configuration); - } - - ServiceConfigurationParameter timeOut = configuration.getParameter("timeout"); - if (timeOut == null) { - timeOut = new ServiceConfigurationParameter(); - timeOut.setName("Timeout (in ms)"); - timeOut.setKey("timeout"); - timeOut.setValue("60000"); - if ("app4mc_sim".equals(csd.getKey())) { - timeOut.setValue("-1"); - } - timeOut.setType("Long"); - timeOut.setManagerParameter(true); - configuration.addParameter(timeOut); - } - - ServiceConfigurationParameter deleteResult = configuration.getParameter("deleteResult"); - if (deleteResult == null) { - deleteResult = new ServiceConfigurationParameter(); - deleteResult.setName("Delete result "); - deleteResult.setKey("deleteResult"); - deleteResult.setValue("true"); - deleteResult.setType("boolean"); - deleteResult.setManagerParameter(true); - configuration.addParameter(deleteResult); - } - } - } - @PreDestroy public void dispose() { this.executor.shutdownNow(); diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java index 99d4f25..74bf577 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatus.java @@ -16,18 +16,23 @@ package org.eclipse.app4mc.cloud.manager; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @JsonSerialize(using = WorkflowStatusSerializer.class) public class WorkflowStatus { + private static final Logger LOGGER = LoggerFactory.getLogger(WorkflowStatus.class); + private String name; private String uuid; - private ArrayList<CloudServiceDefinition> selectedServices = new ArrayList<>(); - private HashMap<String, ServiceConfiguration> serviceConfigurations = new LinkedHashMap<>(); + private ServiceNode rootNode = new ServiceNode("root"); + private ArrayList<String> messages = new ArrayList<>(); private ArrayList<String> errors = new ArrayList<>(); private HashMap<String, String> results = new LinkedHashMap<>(); @@ -43,6 +48,11 @@ public class WorkflowStatus { this.name = name; } + /** + * + * @return The unique id of the workflow execution or <code>null</code> if the + * execution was not performed yet. + */ public String getUuid() { return uuid; } @@ -51,32 +61,178 @@ public class WorkflowStatus { this.uuid = uuid; } - public ArrayList<CloudServiceDefinition> getSelectedServices() { - return selectedServices; + /** + * + * @return The services on root level configured for this {@link WorkflowStatus}. + */ + public List<ServiceNode> getSelectedServices() { + return this.rootNode.getChildren(); + } + + /** + * Add a {@link CloudServiceDefinition} and its corresponding + * {@link ServiceConfiguration} on the root level of the services to execute by + * this {@link WorkflowStatus}. + * + * @param service The {@link CloudServiceDefinition} to add. + * @param config The {@link ServiceConfiguration} for the given + * {@link CloudServiceDefinition}. Can be <code>null</code>. + */ + public void addSelectedService(CloudServiceDefinition service, ServiceConfiguration config) { + this.rootNode.addChild(new ServiceNode(service, config)); } - public void addSelectedService(CloudServiceDefinition service) { - this.selectedServices.add(service); + /** + * Add a {@link CloudServiceDefinition} and its corresponding + * {@link ServiceConfiguration} as a child of an already registered service node + * denoted by the given parent key. + * + * @param parentKey The key to identify an existing service node. A service path + * can be represented as key by concatenating the keys with a + * dot, e.g. migration.validation if validation is a child of + * migration. Note: root should not be added as part of the key. + * @param service The {@link CloudServiceDefinition} to add. + * @param config The {@link ServiceConfiguration} for the given + * {@link CloudServiceDefinition}. Can be <code>null</code>. + */ + public void addSelectedService(String parentKey, CloudServiceDefinition service, ServiceConfiguration config) { + String[] keys = parentKey.split("\\."); + ServiceNode parentNode = this.rootNode; + ServiceNode node = null; + for (int i = 0; i < keys.length; i++) { + String key = keys[i]; + node = parentNode.getChildren().stream() + .filter(child -> child.getId().equals(key)) + .findFirst() + .orElse(null); + + if (i == (keys.length - 1)) { + if (node != null) { + // we found a node, so simply add the child + node.addChild(new ServiceNode(service, config)); + } else if (node == null) { + // we did not find the node, so probably a structural node should be added + node = new ServiceNode(key); + node.addChild(new ServiceNode(service, config)); + parentNode.addChild(node); + } + } + + parentNode = node; + } } - public void removeSelectedService(CloudServiceDefinition service) { - this.selectedServices.remove(service); + /** + * Adds all provided {@link ServiceNode}s on root level of this + * {@link WorkflowStatus}. Used for copy mechanisms. + * + * @param nodes The nodes to add on root level. + */ + public void addAllSelectedServices(List<ServiceNode> nodes) { + nodes.forEach(this.rootNode::addChild); } - public ServiceConfiguration getConfiguration(String key) { - return this.serviceConfigurations.get(key); + /** + * Removes the given {@link CloudServiceDefinition} from the root level of this + * {@link WorkflowStatus}. + * + * @param service The {@link CloudServiceDefinition} to remove. + */ + public void removeSelectedService(CloudServiceDefinition service) { + this.rootNode.removeChild(service); + } + + /** + * Removes the given {@link CloudServiceDefinition} from the service node + * denoted by the given parent key. + * + * @param parentKey The key to identify the existing service node from which the + * given service should be removed. A service path can be + * represented as key by concatenating the keys with a dot, + * e.g. migration.validation if validation is a child of + * migration. Note: root should not be added as part of the + * key. + * @param service The {@link CloudServiceDefinition} to remove. + */ + public void removeSelectedService(String parentKey, CloudServiceDefinition service) { + ServiceNode node = getServiceNode(parentKey); + if (node != null) { + node.removeChild(service); + } } - public HashMap<String, ServiceConfiguration> getConfigurations() { - return this.serviceConfigurations; + /** + * Removes a service node denoted by the given key. + * + * @param key The key to identify the service node to remove. A service path can + * be represented as key by concatenating the keys with a dot, e.g. + * migration.validation if validation is a child of migration. Note: + * root should not be added as part of the key. + */ + public void removeServiceNode(String key) { + // find the parent node + ServiceNode node = key.contains(".") ? getServiceNode(key.substring(0, key.lastIndexOf('.'))) : this.rootNode; + node.removeChild(key.substring(key.lastIndexOf('.') + 1)); } - public void addConfiguration(String key, ServiceConfiguration config) { - this.serviceConfigurations.put(key, config); + /** + * + * @param key The key for which the {@link CloudServiceDefinition} is requested. A + * key is typically the key of a {@link CloudServiceDefinition}. It + * is possible to provide a key path by concatenating keys with a + * dot, e.g. migration.validation in case validation is a child of + * migration. + * @return The {@link CloudServiceDefinition} for the specified key or + * <code>null</code> if there is no {@link CloudServiceDefinition} for the + * specified key registered. + */ + public CloudServiceDefinition getService(String key) { + ServiceNode node = getServiceNode(key); + if (node != null) { + return node.getService(); + } + return null; } - public void removeConfiguration(String key) { - this.serviceConfigurations.remove(key); + /** + * + * @param key The key for which the {@link ServiceConfiguration} is requested. A + * key is typically the key of a {@link CloudServiceDefinition}. It + * is possible to provide a key path by concatenating keys with a + * dot, e.g. migration.validation in case validation is a child of + * migration. + * @return The {@link ServiceConfiguration} for the specified key or + * <code>null</code> if there is no {@link ServiceConfiguration} for the + * specified key registered. + */ + public ServiceConfiguration getConfiguration(String key) { + ServiceNode node = getServiceNode(key); + if (node != null) { + return node.getServiceConfiguration(); + } + return null; + } + + private ServiceNode getServiceNode(String nodeKey) { + String[] keys = nodeKey.split("\\."); + ServiceNode parentNode = this.rootNode; + ServiceNode node = null; + for (int i = 0; i < keys.length; i++) { + String key = keys[i]; + node = parentNode.getChildren().stream() + .filter(child -> child.getId().equals(key)) + .findFirst() + .orElse(null); + + if (node == null) { + LOGGER.warn("Could not resolve key '{}' in nodeKey '{}'", key, nodeKey); + break; + } + + parentNode = node; + } + + return node; } public ArrayList<String> getMessages() { @@ -126,8 +282,7 @@ public class WorkflowStatus { } public void clear() { - this.selectedServices.clear(); - this.serviceConfigurations.clear(); + this.rootNode.clearChildren(); this.messages.clear(); this.errors.clear(); this.results.clear(); diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusDeserializer.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusDeserializer.java index 25a4aa2..fbdbadc 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusDeserializer.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusDeserializer.java @@ -14,7 +14,9 @@ package org.eclipse.app4mc.cloud.manager; import java.io.IOException; +import java.util.Iterator; import java.util.List; +import java.util.Map; import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition; @@ -61,27 +63,7 @@ public class WorkflowStatusDeserializer extends StdDeserializer<WorkflowStatus> JsonNode services = node.get("services"); if (services != null) { services.fields().forEachRemaining(serviceField -> { - // resolve the service - String serviceKey = serviceField.getKey(); - CloudServiceDefinition csd = this.cloudServiceDefinitions.stream() - .filter(sd -> sd.getKey().equals(serviceKey)) - .findFirst() - .orElse(null); - if (csd != null) { - result.addSelectedService(csd); - - // resolve the service configuration - ServiceConfiguration config = WorkflowStatusHelper.getConfigurationForService(csd); - if (config != null) { - serviceField.getValue().fields().forEachRemaining(configField -> { - ServiceConfigurationParameter param = config.getParameter(configField.getKey()); - if (param != null) { - param.setValue(configField.getValue().asText()); - } - }); - result.addConfiguration(serviceKey, config); - } - } + deserializeServiceNode(result, null, serviceField.getKey(), serviceField.getValue().fields()); }); } @@ -111,4 +93,51 @@ public class WorkflowStatusDeserializer extends StdDeserializer<WorkflowStatus> return result; } + + private void deserializeServiceNode( + WorkflowStatus result, + String parentKey, + String serviceKey, + Iterator<Map.Entry<String, JsonNode>> fields) { + + CloudServiceDefinition csd = this.cloudServiceDefinitions.stream() + .filter(sd -> sd.getKey().equals(serviceKey)) + .findFirst() + .orElse(null); + if (csd != null) { + // resolve the service configuration + ServiceConfiguration config = WorkflowStatusHelper.getConfigurationForService(csd); + + // add the service to the workflow status so that the recursive calls work for nested service nodes + if (parentKey == null) { + result.addSelectedService(csd, config); + } else { + result.addSelectedService(parentKey, csd, config); + } + + fields.forEachRemaining(configField -> { + if (!configField.getValue().isContainerNode()) { + // service parameter are simple values, no containers + ServiceConfigurationParameter param = config.getParameter(configField.getKey()); + if (param != null) { + param.setValue(configField.getValue().asText()); + } + } else { + // resolve nested services + deserializeServiceNode( + result, + parentKey != null ? parentKey + "." + serviceKey : serviceKey, + configField.getKey(), + configField.getValue().fields()); + } + }); + } else { + // there is no service definition for the given key, so it is probably a structural node + String nodeKey = parentKey != null ? parentKey + "." + serviceKey : serviceKey; + + fields.forEachRemaining(serviceField -> { + deserializeServiceNode(result, nodeKey, serviceField.getKey(), serviceField.getValue().fields()); + }); + } + } } diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java index c815aa9..7b91cc7 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelper.java @@ -134,6 +134,60 @@ public final class WorkflowStatusHelper { } /** + * Helper method that adds default configuration parameter to a + * {@link WorkflowStatus} in case they are not set. + * + * @param workflowStatus The {@link WorkflowStatus} that should be enriched by + * default configuration parameter. + */ + public static void addDefaultConfigurations(WorkflowStatus workflowStatus) { + for (ServiceNode node : workflowStatus.getSelectedServices()) { + addDefaultConfigurations(node); + } + } + + /** + * Helper method that adds default configuration parameter to a + * {@link ServiceNode} in case they are not set. + * + * @param node The {@link ServiceNode} that should be enriched by + * default configuration parameter. + */ + public static void addDefaultConfigurations(ServiceNode node) { + // check if a configuration is already available + ServiceConfiguration configuration = node.getServiceConfiguration(); + + ServiceConfigurationParameter timeOut = configuration.getParameter("timeout"); + if (timeOut == null) { + timeOut = new ServiceConfigurationParameter(); + timeOut.setName("Timeout (in ms)"); + timeOut.setKey("timeout"); + timeOut.setValue("60000"); + if ("app4mc_sim".equals(node.getService().getKey())) { + timeOut.setValue("-1"); + } + timeOut.setType("Long"); + timeOut.setManagerParameter(true); + configuration.addParameter(timeOut); + } + + ServiceConfigurationParameter deleteResult = configuration.getParameter("deleteResult"); + if (deleteResult == null) { + deleteResult = new ServiceConfigurationParameter(); + deleteResult.setName("Delete result "); + deleteResult.setKey("deleteResult"); + deleteResult.setValue("true"); + deleteResult.setType("boolean"); + deleteResult.setManagerParameter(true); + configuration.addParameter(deleteResult); + } + + for (ServiceNode child : node.getChildren()) { + addDefaultConfigurations(child); + } + } + + /** * Serializes the given {@link WorkflowStatus} to the provided {@link File} as JSON. * * @param ws The {@link WorkflowStatus} to serialize. diff --git a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusSerializer.java b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusSerializer.java index 5b4c41f..2f487bc 100644 --- a/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusSerializer.java +++ b/manager/src/main/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusSerializer.java @@ -52,20 +52,8 @@ public class WorkflowStatusSerializer extends StdSerializer<WorkflowStatus> { gen.writeFieldName("services"); gen.writeStartObject(); - for (CloudServiceDefinition csd : value.getSelectedServices()) { - gen.writeFieldName(csd.getKey()); - gen.writeStartObject(); - - ServiceConfiguration configuration = value.getConfiguration(csd.getKey()); - if (configuration != null) { - for (ServiceConfigurationParameter param : configuration.getParameterList()) { - if (param.getValue() != null) { - gen.writeObjectField(param.getKey(), param.getValue()); - } - } - } - - gen.writeEndObject(); + for (ServiceNode node : value.getSelectedServices()) { + serializeServiceNode(node, gen); } gen.writeEndObject(); @@ -89,4 +77,28 @@ public class WorkflowStatusSerializer extends StdSerializer<WorkflowStatus> { gen.writeEndObject(); } + + private void serializeServiceNode(ServiceNode node, JsonGenerator gen) throws IOException { + gen.writeFieldName(node.getId()); + gen.writeStartObject(); + + if (!node.isStructuralNode()) { + ServiceConfiguration configuration = node.getServiceConfiguration(); + if (configuration != null) { + for (ServiceConfigurationParameter param : configuration.getParameterList()) { + if (param.getValue() != null) { + gen.writeObjectField(param.getKey(), param.getValue()); + } + } + } + } + + if (!node.getChildren().isEmpty()) { + for (ServiceNode child : node.getChildren()) { + serializeServiceNode(child, gen); + } + } + + gen.writeEndObject(); + } } diff --git a/manager/src/main/resources/templates/selectedServices.html b/manager/src/main/resources/templates/selectedServices.html index e195987..d8383ae 100644 --- a/manager/src/main/resources/templates/selectedServices.html +++ b/manager/src/main/resources/templates/selectedServices.html @@ -7,16 +7,16 @@ <body> <div th:fragment="servicesList" id="selectedServices" th:object="${workflowStatus}"> <ul class="list-group"> - <li th:each="selected : ${workflowStatus.selectedServices}" class="list-group-item d-flex justify-content-between" th:classappend="*{uuid != null} ? disabled_service : ''"> + <li th:each="selected : *{selectedServices}" class="list-group-item d-flex justify-content-between" th:classappend="*{uuid != null} ? disabled_service : ''"> <p class="p-0 flex-grow-1 plist" - th:text="${selected.name}" - th:title="${selected.description}" + th:text="${selected.service.name}" + th:title="${selected.service.description}" data-toggle="tooltip" th:classappend="*{uuid != null} ? disabled_service : ''">Service</p> <a th:if="*{uuid == null}" - th:attr="onclick=|removeSelectedServices('${selected.key}')|" + th:attr="onclick=|removeSelectedServices('${selected.service.key}')|" class="btn btn-primary btn-sm btn-rmv"> <i class="fas fa-times"></i> </a> @@ -36,17 +36,17 @@ </option> </select><br> - <div th:if="not *{configurations.isEmpty()}"> - <div th:each="config : *{configurations}" class="mb-3"> - <h4 th:text="${config.value.serviceName + ' Configuration'}">Config</h4> - <div th:each="parameter, parameterStatus : ${config.value.parameterList}"> + <div th:if="not *{selectedServices.isEmpty()}"> + <div th:each="node, nodeStatus : *{selectedServices}" class="mb-3"> + <h4 th:text="${node.service.name + ' Configuration'}">Config</h4> + <div th:each="parameter, parameterStatus : ${node.serviceConfiguration.parameterList}"> <!-- single non-boolean value --> <div th:if="${parameter.cardinality == 'single' and parameter.type != 'boolean' and parameter.possibleValues.isEmpty()}"> <label class="form-check-label" th:text="${parameter.name}">Label</label> <input class="form-control" type="text" - th:field="*{configurations[__${config.key}__].parameterList[__${parameterStatus.index}__].value}" + th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}" th:disabled="*{uuid != null}"/> </div> <!-- single boolean value --> @@ -54,7 +54,7 @@ <input class="form-check-input" type="checkbox" - th:field="*{configurations[__${config.key}__].parameterList[__${parameterStatus.index}__].value}" + th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}" th:value="true" th:disabled="*{uuid != null}"> <label class="form-check-label" th:text="${parameter.name}" th:for="${parameter.key}">Label</label> @@ -65,7 +65,7 @@ <div th:each="pv : ${parameter.possibleValues}" class="form-check"> <input type="checkbox" - th:field="*{configurations[__${config.key}__].parameterList[__${parameterStatus.index}__].value}" + th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}" th:value="${pv}" th:disabled="*{uuid != null}"> <label class="form-check-label" th:text="${pv}">Value</label> @@ -76,7 +76,7 @@ <label th:text="${parameter.name}">Label</label> <select class="custom-select" - th:field="*{configurations[__${config.key}__].parameterList[__${parameterStatus.index}__].value}" + th:field="*{selectedServices[__${nodeStatus.index}__].serviceConfiguration.parameterList[__${parameterStatus.index}__].value}" th:disabled="*{uuid != null}"> <option value=""></option> <option diff --git a/manager/src/test/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelperTest.java b/manager/src/test/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelperTest.java index c89cdcd..9d15843 100644 --- a/manager/src/test/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelperTest.java +++ b/manager/src/test/java/org/eclipse/app4mc/cloud/manager/WorkflowStatusHelperTest.java @@ -16,11 +16,13 @@ package org.eclipse.app4mc.cloud.manager; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import org.eclipse.app4mc.cloud.manager.administration.CloudServiceDefinition; import org.junit.jupiter.api.BeforeAll; @@ -33,38 +35,149 @@ import com.fasterxml.jackson.databind.module.SimpleModule; public class WorkflowStatusHelperTest { - private static String JSON_STRING; + private static String JSON_STRING_SIMPLE_CHAIN; + private static String JSON_STRING_ALL_NESTED; + private static String JSON_STRING_STRUCTURED_NESTED; + private static CloudServiceDefinition MIGRATION = new CloudServiceDefinition( + "migration", + "Migration", + "http://localhost:8080/app4mc/converter/", + "Migrates an input model file to model version 0.9.9"); + private static CloudServiceDefinition VALIDATION = new CloudServiceDefinition( + "validation", + "Validation", + "http://localhost:8181/app4mc/validation/", + "Validates the input model file"); + + private static CloudServiceDefinition LABEL_CORE = new CloudServiceDefinition( + "label_core_interpreter", + "Label per Core Interpreter", + "http://localhost:8084/app4mc/interpreter/label-per-core/", + "Parses an Amalthea model to inspect label access operations per core"); + private static CloudServiceDefinition LABEL_MEMORY = new CloudServiceDefinition( + "label_memory_interpreter", + "Label per Memory Interpreter", + "http://localhost:8084/app4mc/interpreter/label-per-memory/", + "Parses an Amalthea model to inspect label access operations per memory"); + private static CloudServiceDefinition LABEL_TASK = new CloudServiceDefinition( + "label_task_interpreter", + "Label per Task Interpreter", + "http://localhost:8084/app4mc/interpreter/label-per-task/", + "Parses an Amalthea model to inspect label access operations per task"); + private static CloudServiceDefinition LABEL_SIZE = new CloudServiceDefinition( + "label_size_interpreter", + "Label Size Interpreter", + "http://localhost:8084/app4mc/interpreter/label-size/", + "Parses an Amalthea model to inspect the number of labels grouped by size"); + + private static CloudServiceDefinition CHART_VISUALIZER = new CloudServiceDefinition( + "chart_visualizer", + "Chart Visualizer", + "http://localhost:8083/app4mc/visualization/barchart/", + "Visualization of interpreter results"); + @BeforeAll public static void beforeAll() { - StringBuilder builder = new StringBuilder(); - builder.append("{").append(System.lineSeparator()); - builder.append(" \"name\" : \"Test Name\",").append(System.lineSeparator()); - builder.append(" \"uuid\" : \"1234\",").append(System.lineSeparator()); - builder.append(" \"services\" : {").append(System.lineSeparator()); - builder.append(" \"label_core_interpreter\" : {").append(System.lineSeparator()); - builder.append(" \"timeout\" : \"360000\",").append(System.lineSeparator()); - builder.append(" \"deleteResult\" : \"false\"").append(System.lineSeparator()); - builder.append(" },").append(System.lineSeparator()); - builder.append(" \"chart_visualizer\" : { }").append(System.lineSeparator()); - builder.append(" },").append(System.lineSeparator()); - builder.append(" \"cancelled\" : false,").append(System.lineSeparator()); - builder.append(" \"done\" : true,").append(System.lineSeparator()); - builder.append(" \"messages\" : [ \"Message One\", \"Message Two\", \"Message Three\" ],").append(System.lineSeparator()); - builder.append(" \"errors\" : [ ],").append(System.lineSeparator()); - builder.append(" \"results\" : {").append(System.lineSeparator()); - builder.append(" \"Label per Core Interpreter Result\" : \"_label per core interpreter/nodesdata.json\",").append(System.lineSeparator()); - builder.append(" \"Chart Visualizer Result\" : \"_chart visualizer/barchart.html\"").append(System.lineSeparator()); - builder.append(" }").append(System.lineSeparator()); - builder.append("}"); - - JSON_STRING = builder.toString(); + StringBuilder simpleChainBuilder = new StringBuilder(); + simpleChainBuilder.append("{").append(System.lineSeparator()); + simpleChainBuilder.append(" \"name\" : \"Test Name\",").append(System.lineSeparator()); + simpleChainBuilder.append(" \"uuid\" : \"1234\",").append(System.lineSeparator()); + simpleChainBuilder.append(" \"services\" : {").append(System.lineSeparator()); + simpleChainBuilder.append(" \"label_core_interpreter\" : {").append(System.lineSeparator()); + simpleChainBuilder.append(" \"timeout\" : \"360000\",").append(System.lineSeparator()); + simpleChainBuilder.append(" \"deleteResult\" : \"false\"").append(System.lineSeparator()); + simpleChainBuilder.append(" },").append(System.lineSeparator()); + simpleChainBuilder.append(" \"chart_visualizer\" : { }").append(System.lineSeparator()); + simpleChainBuilder.append(" },").append(System.lineSeparator()); + simpleChainBuilder.append(" \"cancelled\" : false,").append(System.lineSeparator()); + simpleChainBuilder.append(" \"done\" : true,").append(System.lineSeparator()); + simpleChainBuilder.append(" \"messages\" : [ \"Message One\", \"Message Two\", \"Message Three\" ],").append(System.lineSeparator()); + simpleChainBuilder.append(" \"results\" : {").append(System.lineSeparator()); + simpleChainBuilder.append(" \"Label per Core Interpreter Result\" : \"_label per core interpreter/nodesdata.json\",").append(System.lineSeparator()); + simpleChainBuilder.append(" \"Chart Visualizer Result\" : \"_chart visualizer/barchart.html\"").append(System.lineSeparator()); + simpleChainBuilder.append(" }").append(System.lineSeparator()); + simpleChainBuilder.append("}"); + + JSON_STRING_SIMPLE_CHAIN = simpleChainBuilder.toString(); + + StringBuilder allNestedBuilder = new StringBuilder(); + allNestedBuilder.append("{").append(System.lineSeparator()); + allNestedBuilder.append(" \"services\" : {").append(System.lineSeparator()); + allNestedBuilder.append(" \"migration\" : {").append(System.lineSeparator()); + allNestedBuilder.append(" \"timeout\" : \"60000\",").append(System.lineSeparator()); + allNestedBuilder.append(" \"deleteResult\" : \"true\",").append(System.lineSeparator()); + allNestedBuilder.append(" \"validation\" : {").append(System.lineSeparator()); + allNestedBuilder.append(" \"timeout\" : \"60000\",").append(System.lineSeparator()); + allNestedBuilder.append(" \"deleteResult\" : \"true\",").append(System.lineSeparator()); + allNestedBuilder.append(" \"label_core_interpreter\" : { }").append(System.lineSeparator()); + allNestedBuilder.append(" }").append(System.lineSeparator()); + allNestedBuilder.append(" }").append(System.lineSeparator()); + allNestedBuilder.append(" }").append(System.lineSeparator()); + allNestedBuilder.append("}"); + + JSON_STRING_ALL_NESTED = allNestedBuilder.toString(); + + StringBuilder structuredNestedBuilder = new StringBuilder(); + structuredNestedBuilder.append("{").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"name\" : \"Test Name\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"uuid\" : \"1234\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"services\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"migration\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"timeout\" : \"60000\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"deleteResult\" : \"true\"").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"validation\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"timeout\" : \"60000\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"deleteResult\" : \"true\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"profiles\" : \"Amalthea Standard Validations\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"label_core_chart\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"label_core_interpreter\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"timeout\" : \"360000\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"deleteResult\" : \"false\"").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"chart_visualizer\" : { }").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"label_memory_chart\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"label_memory_interpreter\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"timeout\" : \"60000\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"deleteResult\" : \"true\"").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"chart_visualizer\" : { }").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"label_task_chart\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"label_task_interpreter\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"timeout\" : \"60000\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"deleteResult\" : \"true\"").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"chart_visualizer\" : { }").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"label_size_chart\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"label_size_interpreter\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"timeout\" : \"60000\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"deleteResult\" : \"true\"").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"chart_visualizer\" : { }").append(System.lineSeparator()); + structuredNestedBuilder.append(" }").append(System.lineSeparator()); + structuredNestedBuilder.append(" }").append(System.lineSeparator()); + structuredNestedBuilder.append(" },").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"cancelled\" : false,").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"done\" : true,").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"messages\" : [ \"Message One\", \"Message Two\", \"Message Three\" ],").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"results\" : {").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"Label per Core Interpreter Result\" : \"_label per core interpreter/nodesdata.json\",").append(System.lineSeparator()); + structuredNestedBuilder.append(" \"Chart Visualizer Result\" : \"_chart visualizer/barchart.html\"").append(System.lineSeparator()); + structuredNestedBuilder.append(" }").append(System.lineSeparator()); + structuredNestedBuilder.append("}"); + + JSON_STRING_STRUCTURED_NESTED = structuredNestedBuilder.toString(); } @Test public void shouldSerializeWorkflowStatus() throws JsonProcessingException { - CloudServiceDefinition service1 = new CloudServiceDefinition("label_core_interpreter", "Label per Core Interpreter", "http://localhost:8080/test", "BlaBlaBla"); - CloudServiceDefinition service2 = new CloudServiceDefinition("chart_visualizer", "Chart Visualizer", "http://localhost:8080/another", "FuBar"); + ServiceConfiguration config1 = WorkflowStatusHelper.getConfigurationForService(LABEL_CORE); + config1.getParameter("timeout").setValue("360000"); + config1.getParameter("deleteResult").setValue("false"); ObjectMapper mapper = new ObjectMapper(); @@ -73,14 +186,9 @@ public class WorkflowStatusHelperTest { ws.setUuid("1234"); ws.done(); - ws.addSelectedService(service1); - ws.addSelectedService(service2); + ws.addSelectedService(LABEL_CORE, config1); + ws.addSelectedService(CHART_VISUALIZER, null); - ServiceConfiguration config1 = WorkflowStatusHelper.getConfigurationForService(service1); - config1.getParameter("timeout").setValue("360000"); - config1.getParameter("deleteResult").setValue("false"); - ws.addConfiguration(service1.getKey(), config1); - ws.addMessage("Message One"); ws.addMessage("Message Two"); ws.addMessage("Message Three"); @@ -90,20 +198,17 @@ public class WorkflowStatusHelperTest { String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(ws); - assertEquals(JSON_STRING, json); + assertEquals(JSON_STRING_SIMPLE_CHAIN, json); } @Test public void shouldDeserializeWorkflowStatus() throws JsonMappingException, JsonProcessingException { - CloudServiceDefinition service1 = new CloudServiceDefinition("label_core_interpreter", "Label per Core Interpreter", "http://localhost:8080/test", "BlaBlaBla"); - CloudServiceDefinition service2 = new CloudServiceDefinition("chart_visualizer", "Chart Visualizer", "http://localhost:8080/another", "FuBar"); - ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); - module.addDeserializer(WorkflowStatus.class, new WorkflowStatusDeserializer(Arrays.asList(service1, service2))); + module.addDeserializer(WorkflowStatus.class, new WorkflowStatusDeserializer(Arrays.asList(LABEL_CORE, CHART_VISUALIZER))); mapper.registerModule(module); - WorkflowStatus ws = mapper.readValue(JSON_STRING, WorkflowStatus.class); + WorkflowStatus ws = mapper.readValue(JSON_STRING_SIMPLE_CHAIN, WorkflowStatus.class); assertEquals("Test Name", ws.getName()); assertEquals("1234", ws.getUuid()); @@ -127,24 +232,281 @@ public class WorkflowStatusHelperTest { assertEquals("_label per core interpreter/nodesdata.json", results.get("Label per Core Interpreter Result")); assertEquals("_chart visualizer/barchart.html", results.get("Chart Visualizer Result")); - ArrayList<CloudServiceDefinition> services = ws.getSelectedServices(); + List<ServiceNode> services = ws.getSelectedServices(); assertNotNull(services); assertEquals(2, services.size()); - assertEquals("label_core_interpreter", services.get(0).getKey()); - assertEquals("Label per Core Interpreter", services.get(0).getName()); - assertEquals("http://localhost:8080/test", services.get(0).getBaseUrl()); - assertEquals("BlaBlaBla", services.get(0).getDescription()); - assertEquals("chart_visualizer", services.get(1).getKey()); - assertEquals("Chart Visualizer", services.get(1).getName()); - assertEquals("http://localhost:8080/another", services.get(1).getBaseUrl()); - assertEquals("FuBar", services.get(1).getDescription()); - - HashMap<String,ServiceConfiguration> configurations = ws.getConfigurations(); - assertEquals(2, configurations.size()); - assertEquals("360000", configurations.get("label_core_interpreter").getParameter("timeout").getValue()); - assertEquals("false", configurations.get("label_core_interpreter").getParameter("deleteResult").getValue()); + assertEquals("label_core_interpreter", services.get(0).getService().getKey()); + assertEquals("Label per Core Interpreter", services.get(0).getService().getName()); + assertEquals("http://localhost:8084/app4mc/interpreter/label-per-core/", services.get(0).getService().getBaseUrl()); + assertEquals("Parses an Amalthea model to inspect label access operations per core", services.get(0).getService().getDescription()); + assertEquals(0, services.get(0).getChildren().size()); + assertEquals("chart_visualizer", services.get(1).getService().getKey()); + assertEquals("Chart Visualizer", services.get(1).getService().getName()); + assertEquals("http://localhost:8083/app4mc/visualization/barchart/", services.get(1).getService().getBaseUrl()); + assertEquals("Visualization of interpreter results", services.get(1).getService().getDescription()); + assertEquals(0, services.get(1).getChildren().size()); + + assertEquals("360000", ws.getConfiguration("label_core_interpreter").getParameter("timeout").getValue()); + assertEquals("false", ws.getConfiguration("label_core_interpreter").getParameter("deleteResult").getValue()); // check that the default configuration set as no configuration is in the JSON - assertEquals("60000", configurations.get("chart_visualizer").getParameter("timeout").getValue()); - assertEquals("true", configurations.get("chart_visualizer").getParameter("deleteResult").getValue()); + assertEquals("60000", ws.getConfiguration("chart_visualizer").getParameter("timeout").getValue()); + assertEquals("true", ws.getConfiguration("chart_visualizer").getParameter("deleteResult").getValue()); + } + + + @Test + public void shouldSerializeAllNestedServices() throws JsonProcessingException { + WorkflowStatus ws = new WorkflowStatus(); + ws.addSelectedService(MIGRATION, WorkflowStatusHelper.getConfigurationForService(MIGRATION)); + ws.addSelectedService("migration", VALIDATION, WorkflowStatusHelper.getConfigurationForService(VALIDATION)); + ws.addSelectedService("migration.validation", LABEL_CORE, null); + + ObjectMapper mapper = new ObjectMapper(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(ws); + + assertEquals(JSON_STRING_ALL_NESTED, json); + } + + @Test + public void shouldDeserializeAllNestedServices() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addDeserializer(WorkflowStatus.class, new WorkflowStatusDeserializer(Arrays.asList(MIGRATION, VALIDATION, LABEL_CORE))); + mapper.registerModule(module); + + WorkflowStatus ws = mapper.readValue(JSON_STRING_ALL_NESTED, WorkflowStatus.class); + + List<ServiceNode> services = ws.getSelectedServices(); + assertNotNull(services); + assertEquals(1, services.size()); + assertEquals("migration", services.get(0).getService().getKey()); + assertEquals(1, services.get(0).getChildren().size()); + + ServiceNode child = services.get(0).getChildren().get(0); + assertEquals("validation", child.getService().getKey()); + assertEquals(1, child.getChildren().size()); + + child = child.getChildren().get(0); + assertEquals("label_core_interpreter", child.getService().getKey()); + assertEquals(0, child.getChildren().size()); + } + + @Test + public void shouldSerializeWorkflowStatusStructuredNested() throws JsonProcessingException { + ServiceConfiguration validationConfig = WorkflowStatusHelper.getConfigurationForService(VALIDATION); + ServiceConfigurationParameter validationProfiles = new ServiceConfigurationParameter(); + validationProfiles.setName("Validation profiles"); + validationProfiles.setKey("profiles"); + validationProfiles.setType("String"); + validationProfiles.setCardinality("multiple"); + validationProfiles.setPossibleValues(Arrays.asList("Amalthea Standard Validations", "INCHRON Validations")); + validationConfig.addParameter(validationProfiles); + + validationConfig.getParameter("profiles").setValue("Amalthea Standard Validations"); + + ServiceConfiguration labelCoreConfig = WorkflowStatusHelper.getConfigurationForService(LABEL_CORE); + labelCoreConfig.getParameter("timeout").setValue("360000"); + labelCoreConfig.getParameter("deleteResult").setValue("false"); + + ObjectMapper mapper = new ObjectMapper(); + + WorkflowStatus ws = new WorkflowStatus(); + ws.setName("Test Name"); + ws.setUuid("1234"); + ws.done(); + + ws.addSelectedService(MIGRATION, WorkflowStatusHelper.getConfigurationForService(MIGRATION)); + ws.addSelectedService(VALIDATION, validationConfig); + + // create structural nodes for sub chains + ws.addSelectedService("validation.label_core_chart", LABEL_CORE, labelCoreConfig); + ws.addSelectedService("validation.label_core_chart", CHART_VISUALIZER, null); + + ws.addSelectedService("validation.label_memory_chart", LABEL_MEMORY, WorkflowStatusHelper.getConfigurationForService(LABEL_MEMORY)); + ws.addSelectedService("validation.label_memory_chart", CHART_VISUALIZER, null); + + ws.addSelectedService("validation.label_task_chart", LABEL_TASK, WorkflowStatusHelper.getConfigurationForService(LABEL_TASK)); + ws.addSelectedService("validation.label_task_chart", CHART_VISUALIZER, null); + + ws.addSelectedService("validation.label_size_chart", LABEL_SIZE, WorkflowStatusHelper.getConfigurationForService(LABEL_SIZE)); + ws.addSelectedService("validation.label_size_chart", CHART_VISUALIZER, null); + + + ws.addMessage("Message One"); + ws.addMessage("Message Two"); + ws.addMessage("Message Three"); + + ws.addResult("Label per Core Interpreter Result", "_label per core interpreter/nodesdata.json"); + ws.addResult("Chart Visualizer Result", "_chart visualizer/barchart.html"); + + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(ws); + + assertEquals(JSON_STRING_STRUCTURED_NESTED, json); + } + + @Test + public void shouldDeserializeWorkflowStatusStructuredNested() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addDeserializer( + WorkflowStatus.class, + new WorkflowStatusDeserializer(Arrays.asList(MIGRATION, VALIDATION, LABEL_CORE, LABEL_MEMORY, LABEL_TASK, LABEL_SIZE, CHART_VISUALIZER))); + mapper.registerModule(module); + + WorkflowStatus ws = mapper.readValue(JSON_STRING_STRUCTURED_NESTED, WorkflowStatus.class); + + List<ServiceNode> services = ws.getSelectedServices(); + assertNotNull(services); + assertEquals(2, services.size()); + assertEquals("migration", services.get(0).getService().getKey()); + assertEquals(0, services.get(0).getChildren().size()); + + assertEquals("validation", services.get(1).getService().getKey()); + // validation has 4 structure nodes for 4 different sub process chains + assertEquals(4, services.get(1).getChildren().size()); + + ServiceNode child = services.get(1).getChildren().get(0); + assertTrue(child.isStructuralNode(), "child is not a structural node"); + assertEquals("label_core_chart", child.getId()); + assertEquals(2, child.getChildren().size()); + + ServiceNode subChild = child.getChildren().get(0); + assertFalse(subChild.isStructuralNode(), "child is a structural node"); + assertNotNull(subChild.getService()); + assertEquals("label_core_interpreter", subChild.getId()); + assertEquals("label_core_interpreter", subChild.getService().getKey()); + assertEquals("360000", subChild.getServiceConfiguration().getParameter("timeout").getValue()); + assertEquals("false", subChild.getServiceConfiguration().getParameter("deleteResult").getValue()); + + subChild = child.getChildren().get(1); + assertFalse(subChild.isStructuralNode(), "child is a structural node"); + assertNotNull(subChild.getService()); + assertEquals("chart_visualizer", subChild.getId()); + assertEquals("chart_visualizer", subChild.getService().getKey()); + assertEquals("60000", subChild.getServiceConfiguration().getParameter("timeout").getValue()); + assertEquals("true", subChild.getServiceConfiguration().getParameter("deleteResult").getValue()); + + child = services.get(1).getChildren().get(1); + assertTrue(child.isStructuralNode(), "child is not a structural node"); + assertEquals("label_memory_chart", child.getId()); + assertEquals(2, child.getChildren().size()); + + subChild = child.getChildren().get(0); + assertFalse(subChild.isStructuralNode(), "child is a structural node"); + assertNotNull(subChild.getService()); + assertEquals("label_memory_interpreter", subChild.getId()); + assertEquals("label_memory_interpreter", subChild.getService().getKey()); + assertEquals("60000", subChild.getServiceConfiguration().getParameter("timeout").getValue()); + assertEquals("true", subChild.getServiceConfiguration().getParameter("deleteResult").getValue()); + + subChild = child.getChildren().get(1); + assertFalse(subChild.isStructuralNode(), "child is a structural node"); + assertNotNull(subChild.getService()); + assertEquals("chart_visualizer", subChild.getId()); + assertEquals("chart_visualizer", subChild.getService().getKey()); + assertEquals("60000", subChild.getServiceConfiguration().getParameter("timeout").getValue()); + assertEquals("true", subChild.getServiceConfiguration().getParameter("deleteResult").getValue()); + + child = services.get(1).getChildren().get(2); + assertTrue(child.isStructuralNode(), "child is not a structural node"); + assertEquals("label_task_chart", child.getId()); + assertEquals(2, child.getChildren().size()); + + subChild = child.getChildren().get(0); + assertFalse(subChild.isStructuralNode(), "child is a structural node"); + assertNotNull(subChild.getService()); + assertEquals("label_task_interpreter", subChild.getId()); + assertEquals("label_task_interpreter", subChild.getService().getKey()); + assertEquals("60000", subChild.getServiceConfiguration().getParameter("timeout").getValue()); + assertEquals("true", subChild.getServiceConfiguration().getParameter("deleteResult").getValue()); + + subChild = child.getChildren().get(1); + assertFalse(subChild.isStructuralNode(), "child is a structural node"); + assertNotNull(subChild.getService()); + assertEquals("chart_visualizer", subChild.getId()); + assertEquals("chart_visualizer", subChild.getService().getKey()); + assertEquals("60000", subChild.getServiceConfiguration().getParameter("timeout").getValue()); + assertEquals("true", subChild.getServiceConfiguration().getParameter("deleteResult").getValue()); + + child = services.get(1).getChildren().get(3); + assertTrue(child.isStructuralNode(), "child is not a structural node"); + assertEquals("label_size_chart", child.getId()); + assertEquals(2, child.getChildren().size()); + + subChild = child.getChildren().get(0); + assertFalse(subChild.isStructuralNode(), "child is a structural node"); + assertNotNull(subChild.getService()); + assertEquals("label_size_interpreter", subChild.getId()); + assertEquals("label_size_interpreter", subChild.getService().getKey()); + assertEquals("60000", subChild.getServiceConfiguration().getParameter("timeout").getValue()); + assertEquals("true", subChild.getServiceConfiguration().getParameter("deleteResult").getValue()); + + subChild = child.getChildren().get(1); + assertFalse(subChild.isStructuralNode(), "child is a structural node"); + assertNotNull(subChild.getService()); + assertEquals("chart_visualizer", subChild.getId()); + assertEquals("chart_visualizer", subChild.getService().getKey()); + assertEquals("60000", subChild.getServiceConfiguration().getParameter("timeout").getValue()); + assertEquals("true", subChild.getServiceConfiguration().getParameter("deleteResult").getValue()); + } + + @Test + public void shouldOperateOnWorkflowStatusStructuredNested() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + SimpleModule module = new SimpleModule(); + module.addDeserializer( + WorkflowStatus.class, + new WorkflowStatusDeserializer(Arrays.asList(MIGRATION, VALIDATION, LABEL_CORE, LABEL_MEMORY, LABEL_TASK, LABEL_SIZE, CHART_VISUALIZER))); + mapper.registerModule(module); + + WorkflowStatus ws = mapper.readValue(JSON_STRING_STRUCTURED_NESTED, WorkflowStatus.class); + + // test direct access on services via workflow status using concatenated keys + CloudServiceDefinition service = ws.getService("validation.label_core_chart.chart_visualizer"); + assertEquals(CHART_VISUALIZER, service); + service = ws.getService("validation.label_memory_chart.chart_visualizer"); + assertEquals(CHART_VISUALIZER, service); + service = ws.getService("validation.label_task_chart.chart_visualizer"); + assertEquals(CHART_VISUALIZER, service); + service = ws.getService("validation.label_size_chart.chart_visualizer"); + assertEquals(CHART_VISUALIZER, service); + + assertNull(ws.getService("some.stupid.key")); + + ServiceConfiguration config = ws.getConfiguration("validation.label_core_chart.label_core_interpreter"); + assertEquals("360000", config.getParameter("timeout").getValue()); + assertEquals("false", config.getParameter("deleteResult").getValue()); + + assertNull(ws.getConfiguration("some.stupid.key")); + + List<ServiceNode> services = ws.getSelectedServices(); + assertNotNull(services); + assertEquals(2, services.size()); + assertEquals("migration", services.get(0).getService().getKey()); + + // remove migration on root level + ws.removeSelectedService(MIGRATION); + + services = ws.getSelectedServices(); + assertNotNull(services); + assertEquals(1, services.size()); + assertEquals("validation", services.get(0).getService().getKey()); + assertEquals(4, services.get(0).getChildren().size()); + + // remove structural node label_task_chart + ws.removeServiceNode("validation.label_task_chart"); + + assertEquals(3, services.get(0).getChildren().size()); + + ServiceNode serviceNode = services.get(0).getChildren().get(2); + assertTrue(serviceNode.isStructuralNode()); + assertEquals("label_size_chart", serviceNode.getId()); + assertEquals(2, serviceNode.getChildren().size()); + + // remove chart from label_size_chart + ws.removeSelectedService("validation.label_size_chart", CHART_VISUALIZER); +// ws.removeServiceNode("validation.label_size_chart.chart_visualizer"); + + assertEquals(1, serviceNode.getChildren().size()); } } |