From f1fb6edcc02d1c82fd4d82a161d0e3d860098140 Mon Sep 17 00:00:00 2001 From: Lorenzo Biava Date: Tue, 24 May 2016 12:50:33 +0200 Subject: [PATCH] Add TOSCA input replacement unit tests - Test node's, artifact's, relationships' and capabilities' properties substitution - Default input replaced - Required input not in list - Not required input without default value - Empty input list See #41 --- .../IndigoInputsPreProcessorService.java | 3 +- .../service/ToscaServiceTest.java | 133 ++++++++++++++++-- .../inputs/tosca_inputs_default_replaced.yaml | 25 ++++ .../inputs/tosca_inputs_empty_input_list.yaml | 16 +++ ...a_inputs_not_required_without_default.yaml | 24 ++++ .../tosca_inputs_replaced_all_types.yaml | 101 +++++++++++++ .../tosca_inputs_required_not_given.yaml | 24 ++++ 7 files changed, 314 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/tosca/inputs/tosca_inputs_default_replaced.yaml create mode 100644 src/test/resources/tosca/inputs/tosca_inputs_empty_input_list.yaml create mode 100644 src/test/resources/tosca/inputs/tosca_inputs_not_required_without_default.yaml create mode 100644 src/test/resources/tosca/inputs/tosca_inputs_replaced_all_types.yaml create mode 100644 src/test/resources/tosca/inputs/tosca_inputs_required_not_given.yaml diff --git a/src/main/java/it/reply/orchestrator/service/IndigoInputsPreProcessorService.java b/src/main/java/it/reply/orchestrator/service/IndigoInputsPreProcessorService.java index fdeecfd8cb..0d25145f55 100644 --- a/src/main/java/it/reply/orchestrator/service/IndigoInputsPreProcessorService.java +++ b/src/main/java/it/reply/orchestrator/service/IndigoInputsPreProcessorService.java @@ -145,7 +145,8 @@ protected AbstractPropertyValue processGetInput(Map // Replace property value (was Function, now Scalar) if (inputValue instanceof String || inputValue instanceof Boolean - || inputValue instanceof Integer || inputValue instanceof Double) { + || inputValue instanceof Integer || inputValue instanceof Double + || inputValue instanceof Float) { return new ScalarPropertyValue(inputValue.toString()); } else if (inputValue instanceof List) { return new ListPropertyValue((List) ((List) inputValue).stream() diff --git a/src/test/java/it/reply/orchestrator/service/ToscaServiceTest.java b/src/test/java/it/reply/orchestrator/service/ToscaServiceTest.java index 03198291dd..3c71172d34 100644 --- a/src/test/java/it/reply/orchestrator/service/ToscaServiceTest.java +++ b/src/test/java/it/reply/orchestrator/service/ToscaServiceTest.java @@ -1,9 +1,16 @@ package it.reply.orchestrator.service; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import alien4cloud.model.components.AbstractPropertyValue; +import alien4cloud.model.components.ListPropertyValue; import alien4cloud.model.components.PropertyValue; +import alien4cloud.model.components.ScalarPropertyValue; +import alien4cloud.model.topology.Capability; import alien4cloud.model.topology.NodeTemplate; +import alien4cloud.tosca.model.ArchiveRoot; import alien4cloud.tosca.parser.ParsingException; import es.upv.i3m.grycap.file.NoNullOrEmptyFile; @@ -13,13 +20,15 @@ import it.reply.orchestrator.config.WebAppConfigurationAware; import it.reply.orchestrator.exception.service.ToscaException; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,29 +37,35 @@ public class ToscaServiceTest extends WebAppConfigurationAware { @Autowired private ToscaService toscaService; + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static final String TEMPLATES_BASE_DIR = "./src/test/resources/tosca/"; + private static final String TEMPLATES_INPUT_BASE_DIR = TEMPLATES_BASE_DIR + "inputs/"; + private String deploymentId = "deployment_id"; @Test(expected = ToscaException.class) public void customizeTemplateWithError() throws Exception { - String template = getFileContentAsString( - "./src/test/resources/tosca/galaxy_tosca_clues_error.yaml"); + String template = getFileContentAsString(TEMPLATES_BASE_DIR + "galaxy_tosca_clues_error.yaml"); toscaService.customizeTemplate(template, deploymentId); } + @SuppressWarnings("unchecked") @Test public void customizeTemplateWithDeplymentIdSuccessfully() throws Exception { - String template = getFileContentAsString( - "./src/test/resources/tosca/galaxy_tosca_clues.yaml"); + String template = getFileContentAsString(TEMPLATES_BASE_DIR + "galaxy_tosca_clues.yaml"); String customizedTemplate = toscaService.customizeTemplate(template, deploymentId); String templateDeploymentId = ""; Map nodes = toscaService.getArchiveRootFromTemplate(customizedTemplate) .getResult().getTopology().getNodeTemplates(); for (Map.Entry entry : nodes.entrySet()) { if (entry.getValue().getType().equals("tosca.nodes.indigo.ElasticCluster")) { - templateDeploymentId = ((PropertyValue) entry.getValue().getProperties() - .get("deployment_id")).getValue(); + templateDeploymentId = + ((PropertyValue) entry.getValue().getProperties().get("deployment_id")) + .getValue(); } } @@ -60,15 +75,111 @@ public void customizeTemplateWithDeplymentIdSuccessfully() throws Exception { @Test public void getRemovalList() throws IOException, ParsingException, FileException { List expectedRemovalList = Arrays.asList("to-be-deleted-1", "to-be-deleted-2"); - String template = getFileContentAsString( - "./src/test/resources/tosca/galaxy_tosca_clues_removal_list.yaml"); + String template = + getFileContentAsString(TEMPLATES_BASE_DIR + "galaxy_tosca_clues_removal_list.yaml"); NodeTemplate node = toscaService.getArchiveRootFromTemplate(template).getResult().getTopology() .getNodeTemplates().get("torque_wn"); List removalList = toscaService.getRemovalList(node); assertEquals(expectedRemovalList, removalList); } - - private String getFileContentAsString(String fileUri) throws FileException{ + + @Test + public void checkUserInputDefaultReplaced() throws Exception { + String template = + getFileContentAsString(TEMPLATES_INPUT_BASE_DIR + "tosca_inputs_default_replaced.yaml"); + Map inputs = new HashMap(); + ArchiveRoot ar = toscaService.prepareTemplate(template, inputs); + AbstractPropertyValue numCpus = ar.getTopology().getNodeTemplates().get("my_server") + .getCapabilities().get("host").getProperties().get("num_cpus"); + assertThat(numCpus, instanceOf(PropertyValue.class)); + assertEquals("8", ((PropertyValue) numCpus).getValue()); + } + + @Test + public void checkUserInputReplacedInNodeArtifactsRelationshipsCapabilitiesProperties() + throws Exception { + String template = + getFileContentAsString(TEMPLATES_INPUT_BASE_DIR + "tosca_inputs_replaced_all_types.yaml"); + Map inputs = new HashMap(); + inputs.put("input_urls", Arrays.asList("http://a.it", "http://b.it")); + inputs.put("output_filenames", "test1, test2"); + inputs.put("command", "command"); + inputs.put("cpus", 1.0d); + inputs.put("mem", "256 MB"); + inputs.put("docker_image", "docker_image"); + + ArchiveRoot ar = toscaService.prepareTemplate(template, inputs); + Map nodes = ar.getTopology().getNodeTemplates(); + NodeTemplate chronosJob = nodes.get("chronos_job"); + // Node's properties + assertEquals(inputs.get("command"), + toscaService.getNodePropertyValueByName(chronosJob, "command").getValue()); + // Validate list replacement (little bit hard-coded... should be improved) + AbstractPropertyValue uris = toscaService.getNodePropertyValueByName(chronosJob, "uris"); + assertThat(uris, instanceOf(ListPropertyValue.class)); + AbstractPropertyValue urisOne = + (AbstractPropertyValue) ((ListPropertyValue) uris).getValue().get(0); + AbstractPropertyValue urisTwo = + (AbstractPropertyValue) ((ListPropertyValue) uris).getValue().get(1); + assertThat(urisOne, instanceOf(ScalarPropertyValue.class)); + assertThat(urisTwo, instanceOf(ScalarPropertyValue.class)); + assertEquals(inputs.get("input_urls"), Arrays.asList(((ScalarPropertyValue) urisOne).getValue(), + ((ScalarPropertyValue) urisTwo).getValue())); + + // Recursive node's properties + @SuppressWarnings("unchecked") + AbstractPropertyValue outputFileNames = + (AbstractPropertyValue) (((Map) toscaService + .getNodePropertyValueByName(chronosJob, "environment_variables").getValue()) + .get("OUTPUT_FILENAMES")); + assertThat(outputFileNames, instanceOf(ScalarPropertyValue.class)); + assertEquals(inputs.get("output_filenames").toString(), + ((ScalarPropertyValue) outputFileNames).getValue()); + + // Artifacts' properties + assertEquals(inputs.get("docker_image"), + ((ScalarPropertyValue) chronosJob.getArtifacts().get("image").getFile()).getValue()); + + // Requirements' properties + Map dockerRelationships = + toscaService.getAssociatedNodesByCapability(nodes, chronosJob, "host"); + + NodeTemplate dockerNode = dockerRelationships.values().iterator().next(); + Capability dockerCapability = dockerNode.getCapabilities().get("host"); + assertEquals(inputs.get("cpus").toString(), + toscaService.getCapabilityPropertyValueByName(dockerCapability, "num_cpus").getValue()); + assertEquals(inputs.get("mem").toString(), + toscaService.getCapabilityPropertyValueByName(dockerCapability, "mem_size").getValue()); + + // FIXME: Also test relationships' properties + } + + @Test + public void checkUserInputRequiredInputNotInList() throws Exception { + checkUserInputGeneric("tosca_inputs_required_not_given.yaml", "required and is not present"); + } + + @Test + public void checkUserInputNotRequiredWithoutDefaultValue() throws Exception { + checkUserInputGeneric("tosca_inputs_not_required_without_default.yaml", + "neither required nor has a default value"); + } + + @Test + public void checkUserInputPresentButEmptyInputList() throws Exception { + checkUserInputGeneric("tosca_inputs_empty_input_list.yaml", "Empty template input list"); + } + + private void checkUserInputGeneric(String templateName, String expectedMessage) throws Exception { + thrown.expect(ToscaException.class); + thrown.expectMessage(expectedMessage); + + String template = getFileContentAsString(TEMPLATES_INPUT_BASE_DIR + templateName); + Map inputs = new HashMap(); + toscaService.prepareTemplate(template, inputs); + } + + private String getFileContentAsString(String fileUri) throws FileException { return new NoNullOrEmptyFile(new Utf8File(Paths.get(fileUri))).read(); } } diff --git a/src/test/resources/tosca/inputs/tosca_inputs_default_replaced.yaml b/src/test/resources/tosca/inputs/tosca_inputs_default_replaced.yaml new file mode 100644 index 0000000000..b4e4441b12 --- /dev/null +++ b/src/test/resources/tosca/inputs/tosca_inputs_default_replaced.yaml @@ -0,0 +1,25 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA for testing a required input not given by the user + +topology_template: + + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + required: false + default: 8 + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: { get_input: cpus } + mem_size: 2048 MB + disk_size: 10 GB \ No newline at end of file diff --git a/src/test/resources/tosca/inputs/tosca_inputs_empty_input_list.yaml b/src/test/resources/tosca/inputs/tosca_inputs_empty_input_list.yaml new file mode 100644 index 0000000000..71faeba355 --- /dev/null +++ b/src/test/resources/tosca/inputs/tosca_inputs_empty_input_list.yaml @@ -0,0 +1,16 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA for testing a required input not given by the user + +topology_template: + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: { get_input: cpus } + mem_size: 2048 MB + disk_size: 10 GB \ No newline at end of file diff --git a/src/test/resources/tosca/inputs/tosca_inputs_not_required_without_default.yaml b/src/test/resources/tosca/inputs/tosca_inputs_not_required_without_default.yaml new file mode 100644 index 0000000000..2772b21af6 --- /dev/null +++ b/src/test/resources/tosca/inputs/tosca_inputs_not_required_without_default.yaml @@ -0,0 +1,24 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA for testing a required input not given by the user + +topology_template: + + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + required: false + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: { get_input: cpus } + mem_size: 2048 MB + disk_size: 10 GB \ No newline at end of file diff --git a/src/test/resources/tosca/inputs/tosca_inputs_replaced_all_types.yaml b/src/test/resources/tosca/inputs/tosca_inputs_replaced_all_types.yaml new file mode 100644 index 0000000000..2369c56e3c --- /dev/null +++ b/src/test/resources/tosca/inputs/tosca_inputs_replaced_all_types.yaml @@ -0,0 +1,101 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +imports: + - indigo_custom_types: https://raw.githubusercontent.com/indigo-dc/tosca-types/master/custom_types.yaml + +description: > + TOSCA examples for specifying a Chronos Job that runs an application using the input stored at some URL and uploads the output data to an http(s) or ftp(s) or webdav(s) repository + +topology_template: + + inputs: + input_urls: + type: list + description: List of input files that will be downloaded in the job sandbox (archives will be automatically uncompressed) + entry_schema: + type: string + required: yes + + output_filenames: + type: list + description: List of filenames generated by the application run + entry_schema: + type: string + required: yes + + command: + type: string + description: Command to execute + default: 'env' + required: no + + cpus: + type: float + description: Amount of CPUs for this job + required: yes + + mem: + type: scalar-unit.size + description: Amount of Memory for this job + required: yes + + docker_image: + type: string + description: Docker image to be used to run the container application + required: yes + + node_templates: + chronos_job: + type: tosca.nodes.indigo.Container.Application.Docker.Chronos + properties: + schedule: 'R0/2015-12-25T17:22:00Z/PT1M' + description: 'Execute Application' + command: { get_input: command } + uris: { get_input: input_urls} + retries: 3 + environment_variables: + OUTPUT_FILENAMES: { get_input: output_filenames } + artifacts: + image: + file: { get_input: docker_image } + type: tosca.artifacts.Deployment.Image.Container.Docker + requirements: + - host: docker_runtime1 + + + chronos_job_upload: + type: tosca.nodes.indigo.Container.Application.Docker.Chronos + properties: + description: 'Upload output data' + command: 'echo \"I will upload something...\"' + retries: 3 + artifacts: + image: + file: libmesos/ubuntu + type: tosca.artifacts.Deployment.Image.Container.Docker + requirements: + - host: docker_runtime2 + - parent_job: + node: chronos_job + #relationship: + # type: DependsOn + # properties: + # test: { get_input: cpus } + + + docker_runtime1: + type: tosca.nodes.indigo.Container.Runtime.Docker + capabilities: + host: + properties: + num_cpus: { get_input: cpus } + mem_size: { get_input: mem } + + + docker_runtime2: + type: tosca.nodes.indigo.Container.Runtime.Docker + capabilities: + host: + properties: + num_cpus: 1.0 + mem_size: 1024 MB \ No newline at end of file diff --git a/src/test/resources/tosca/inputs/tosca_inputs_required_not_given.yaml b/src/test/resources/tosca/inputs/tosca_inputs_required_not_given.yaml new file mode 100644 index 0000000000..9043900fcc --- /dev/null +++ b/src/test/resources/tosca/inputs/tosca_inputs_required_not_given.yaml @@ -0,0 +1,24 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA for testing a required input not given by the user + +topology_template: + + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + required: yes + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: { get_input: cpus } + mem_size: 2048 MB + disk_size: 10 GB \ No newline at end of file