diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7b6bb91bf9cf87a3412e89cd79dadef2f8bddf3f..98109fce69469945dc35c2c9e808ca05fef4a109 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -53,6 +53,9 @@ variables:
METASOLVER_CLI: "mvn --batch-mode -N -Dmaven.test.skip=$SKIP_TESTS -Ddocker.push=false -f meta_solver/pom.xml"
MQ_ADAPTER_CLI: "mvn --batch-mode -N -Dmaven.test.skip=$SKIP_TESTS -Ddocker.push=false -f mq-http-adapter/pom.xml"
GUI_BACKEND_CLI: "mvn --batch-mode -N -Dmaven.test.skip=$SKIP_TESTS -Ddocker.push=false -f gui-backend/pom.xml"
+
+ FUNCTIONIZER_TESTING_TOOL_CLI: "mvn --batch-mode -N -Dmaven.test.skip=$SKIP_TESTS -Ddocker.push=false -f functionizer-testing-tool/pom.xml"
+
cache:
paths:
- maven_repo/
@@ -737,3 +740,18 @@ deploy:gui-backend:
- echo "$K8S_SECRET_DOCKER_PASSWORD" | docker login $CI_REGISTRY -u $K8S_SECRET_DOCKER_USER --password-stdin
- docker tag $LOCAL_REPO/melodic/gui-backend:unknown $CI_REGISTRY_IMAGE/gui-backend:$CI_COMMIT_BRANCH
- docker push $CI_REGISTRY_IMAGE/gui-backend:$CI_COMMIT_BRANCH
+
+deploy:functionizer-testing-tool:
+ stage: deploy
+ only:
+ - master
+ - rc3.0
+ - rc3.1
+ image: $DOCKER_DIND_IMAGE
+ services:
+ - $DOCKER_DIND_SERVICE
+ script:
+ - $DOCKER_CLI $FUNCTIONIZER_TESTING_TOOL_CLI -Ddocker.imagePrefix=melodic/ clean install
+ - echo "$K8S_SECRET_DOCKER_PASSWORD" | docker login $CI_REGISTRY -u $K8S_SECRET_DOCKER_USER --password-stdin
+ - docker tag $LOCAL_REPO/melodic/functionizer-testing-tool:unknown $CI_REGISTRY_IMAGE/functionizer-testing-tool:$CI_COMMIT_BRANCH
+ - docker push $CI_REGISTRY_IMAGE/functionizer-testing-tool:$CI_COMMIT_BRANCH
diff --git a/functionizer-testing-tool/pom.xml b/functionizer-testing-tool/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..36111ed6327842ce1d28ec281c086dfb5d10896d
--- /dev/null
+++ b/functionizer-testing-tool/pom.xml
@@ -0,0 +1,187 @@
+
+
+
+ upperware
+ org.ow2.paasage
+ 3.1.0-SNAPSHOT
+
+ 4.0.0
+
+ eu.functionizer
+ functionizer-testing-tool
+ Upperware - Functionizer testing tool
+
+
+ 1.8
+ 3.1.0-SNAPSHOT
+ 3.1.0-SNAPSHOT
+
+ functionizer-testing-tool
+
+
+
+
+ org.springframework
+ spring-beans
+
+
+ org.springframework
+ spring-context
+
+
+ org.springframework
+ spring-core
+
+
+ org.springframework
+ spring-web
+
+
+ org.springframework
+ spring-test
+
+
+
+ org.springframework.boot
+ spring-boot
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.25
+
+
+ org.yaml
+ snakeyaml
+ 1.23
+
+
+
+ org.ow2.paasage
+ melodic-commons
+
+
+
+ org.ow2.paasage
+ jwt-commons
+
+
+
+ com.amazonaws
+ aws-java-sdk-core
+ 1.11.327
+
+
+ com.amazonaws
+ aws-java-sdk-lambda
+ 1.11.327
+
+
+
+ com.azure
+ azure-identity
+ 1.0.4
+
+
+
+ com.microsoft.azure
+ azure-mgmt-resources
+ 1.33.1
+
+
+
+ com.microsoft.azure
+ azure
+ 1.33.1
+
+
+
+ com.microsoft.azure.functions
+ azure-functions-java-library
+ 1.3.1
+
+
+
+ com.google.apis
+ google-api-services-cloudfunctions
+ v1-rev20200219-1.30.9
+
+
+
+ org.assertj
+ assertj-core
+ 3.13.2
+ compile
+
+
+
+ junit
+ junit
+
+
+
+ org.junit.platform
+ junit-platform-launcher
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.5.2
+ compile
+
+
+ org.junit.platform
+ junit-platform-reporting
+ 1.6.2
+
+
+ io.github.cloudiator.client
+ java-rest
+
+
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+ com.spotify
+ docker-maven-plugin
+
+
+ org.codehaus.mojo
+ buildnumber-maven-plugin
+
+
+ org.codehaus.groovy.maven
+ gmaven-plugin
+
+
+
+
+
+
+
+
diff --git a/functionizer-testing-tool/src/main/docker/Dockerfile b/functionizer-testing-tool/src/main/docker/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..50b4981b4549f35646d32a41474833ad18ad700f
--- /dev/null
+++ b/functionizer-testing-tool/src/main/docker/Dockerfile
@@ -0,0 +1,9 @@
+FROM java:8-alpine
+VOLUME /tmp
+ADD functionizer-testing-tool.jar functionizer-testing-tool.jar
+RUN /bin/sh -c "apk add --no-cache bash"
+RUN bash -c 'touch /functionizer-testing-tool.jar'
+
+COPY ./run.sh .
+RUN bash -c 'chmod +rx run.sh'
+ENTRYPOINT ["./run.sh"]
diff --git a/functionizer-testing-tool/src/main/docker/run.sh b/functionizer-testing-tool/src/main/docker/run.sh
new file mode 100644
index 0000000000000000000000000000000000000000..be98ed0704535b4f5d2a156b92cb466f26154c8c
--- /dev/null
+++ b/functionizer-testing-tool/src/main/docker/run.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+/config/wait-for-cdo.sh && java \
+-Duser.timezone=Europe/Warsaw \
+-Djava.security.egd=file:/dev/./urandom \
+-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 \
+-jar functionizer-testing-tool.jar
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplication.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a528667365321cb23f50d4f1bb86a5ed1ac2044
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplication.java
@@ -0,0 +1,17 @@
+package eu.functionizer.functionizertestingtool;
+
+import eu.paasage.upperware.security.authapi.properties.MelodicSecurityProperties;
+import eu.passage.upperware.commons.cloudiator.CloudiatorProperties;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+
+@SpringBootApplication
+@EnableConfigurationProperties({CloudiatorProperties.class, MelodicSecurityProperties.class})
+public class FunctionizerTestingToolApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(FunctionizerTestingToolApplication.class, args);
+ }
+
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java
new file mode 100644
index 0000000000000000000000000000000000000000..24afed03152ad5f4ba06e90b1618fe7e60dfcf67
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java
@@ -0,0 +1,37 @@
+package eu.functionizer.functionizertestingtool;
+
+
+import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult;
+import eu.functionizer.functionizertestingtool.service.TestRunner;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+
+
+@RestController
+@Slf4j
+@AllArgsConstructor(onConstructor = @__(@Autowired))
+public class FunctionizerTestingToolController {
+
+ private TestRunner testRunner;
+
+ @RequestMapping("/")
+ @ResponseStatus(HttpStatus.OK)
+ public String index() {
+ return "Welcome to the Functionizer testing tool!";
+ }
+
+ @GetMapping(value = "/health")
+ @ResponseStatus(HttpStatus.OK)
+ public void health() {
+ }
+
+ @PostMapping("/test")
+ @ResponseStatus(HttpStatus.OK)
+ public FunctionizerTestResult runTests() {
+ return testRunner.runTests();
+ }
+
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..66bc8d6afd0c70d86e78b67e844228f7bffd3e8e
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java
@@ -0,0 +1,16 @@
+package eu.functionizer.functionizertestingtool;
+
+import eu.paasage.upperware.security.authapi.properties.MelodicSecurityProperties;
+import eu.paasage.upperware.security.authapi.token.JWTService;
+import eu.paasage.upperware.security.authapi.token.JWTServiceImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class TestingToolContext {
+
+ @Bean
+ public JWTService jWTService(MelodicSecurityProperties melodicSecurityProperties) {
+ return new JWTServiceImpl(melodicSecurityProperties);
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/WebSecurity.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/WebSecurity.java
new file mode 100644
index 0000000000000000000000000000000000000000..0b3966ff68d6b2727d7d0fc0d85b2a9d73c26e6a
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/WebSecurity.java
@@ -0,0 +1,45 @@
+package eu.functionizer.functionizertestingtool;
+
+import eu.paasage.upperware.security.authapi.JWTAuthorizationFilter;
+import eu.paasage.upperware.security.authapi.token.JWTService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+
+@Slf4j
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+public class WebSecurity extends WebSecurityConfigurerAdapter {
+
+ private final JWTService jwtService;
+
+ @Value("${melodic.security.enabled:true}")
+ private boolean securityEnabled;
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+
+ if (securityEnabled) {
+ log.info("Running WITH security");
+ http.cors().and().csrf().disable().authorizeRequests()
+ .anyRequest().authenticated()
+ .and()
+ .addFilter(new JWTAuthorizationFilter(authenticationManager(), jwtService))
+ // this disables session creation on Spring Security
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+ } else {
+ log.info("Running WITHOUT security");
+ http.csrf().disable()
+ .authorizeRequests()
+ .antMatchers("/**").permitAll()
+ .anyRequest().authenticated();
+ }
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/CloudiatorData.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/CloudiatorData.java
new file mode 100644
index 0000000000000000000000000000000000000000..44990d100bafe39b21a13740a12bc4e647137ebd
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/CloudiatorData.java
@@ -0,0 +1,20 @@
+package eu.functionizer.functionizertestingtool.model;
+
+import io.github.cloudiator.rest.model.Function;
+import io.github.cloudiator.rest.model.NodeCandidate;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Map;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class CloudiatorData {
+ private Map deployedFunctions;
+ private Map functionDeployNames;
+ private Map nodeCandidates;
+ private Map userSecrets;
+
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestResult.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e83036b065c2b77336278997e3e58f4d077f809
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestResult.java
@@ -0,0 +1,56 @@
+package eu.functionizer.functionizertestingtool.model;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+@Data
+public class FunctionTestResult {
+ private String functionName;
+ private double duration;
+ private Long passed;
+ private Long failed;
+ private Long ignored;
+ private List testCaseResults;
+ private TestResultEnum overallResult;
+ private String failedAtStage;
+ private String message;
+
+ public FunctionTestResult(String functionName) {
+ this.functionName = functionName;
+ this.testCaseResults = new ArrayList<>();
+ this.passed = 0L;
+ this.failed = 0L;
+ this.ignored = 0L;
+ this.overallResult = TestResultEnum.SUCCESS;
+ }
+
+ public void addTestCaseResult(TestCaseResult testCaseResult) {
+ this.testCaseResults.add(testCaseResult);
+ TestResultEnum result = testCaseResult.getResult();
+ if (result == TestResultEnum.SUCCESS) {
+ this.passed += 1;
+ } else if (result == TestResultEnum.FAILURE){
+ this.failed += 1;
+ } else if (result == TestResultEnum.IGNORED) {
+ this.ignored += 1;
+ }
+ updateOverallResult();
+ }
+
+ private void updateOverallResult() {
+ if (this.overallResult == TestResultEnum.PARTIAL_FAILURE
+ || this.overallResult == TestResultEnum.IGNORED) {
+ return;
+ }
+ if (this.failed == 0L && this.ignored == 0L) {
+ this.overallResult = TestResultEnum.SUCCESS;
+ } else if (this.passed == 0L) {
+ this.overallResult = TestResultEnum.FAILURE;
+ } else {
+ this.overallResult = TestResultEnum.PARTIAL_FAILURE;
+ }
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionizerTestResult.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionizerTestResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..8dbd9978d44a73af4dbdaac89043aec3d9a621b9
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionizerTestResult.java
@@ -0,0 +1,23 @@
+package eu.functionizer.functionizertestingtool.model;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class FunctionizerTestResult {
+ private List functionTestResults;
+ private TestResultEnum testsRunResult;
+ private String failedAtStage;
+ private String message;
+ private double duration;
+
+ public FunctionizerTestResult() {
+ this.functionTestResults = new ArrayList<>();
+ }
+
+ public void addFunctionTestResult(FunctionTestResult functionTestResult) {
+ this.functionTestResults.add(functionTestResult);
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/ReportEntryKey.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/ReportEntryKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..71b8d99953fd67899130c12ac356fc4087985b1a
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/ReportEntryKey.java
@@ -0,0 +1,10 @@
+package eu.functionizer.functionizertestingtool.model;
+
+public class ReportEntryKey {
+ public final static String EVENT = "event";
+ public final static String EXPECTED_OUTPUT = "expectedOutput";
+ public final static String IGNORED = "ignored";
+ public final static String ROOT = "root";
+ public final static String STAGE = "stage";
+ public final static String FAILURE_CAUSE = "failureCause";
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCaseResult.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCaseResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef3c65b9e4b6c903d3675c74798d3caa215cf0d7
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCaseResult.java
@@ -0,0 +1,13 @@
+package eu.functionizer.functionizertestingtool.model;
+
+import lombok.Data;
+
+@Data
+public class TestCaseResult {
+ private String event;
+ private String expectedOutput;
+ private String actualOutput;
+ private String message;
+ private TestResultEnum result;
+ private double duration;
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestResultEnum.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestResultEnum.java
new file mode 100644
index 0000000000000000000000000000000000000000..4a2100edb579ce09a6953576cdb6ff9019788939
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestResultEnum.java
@@ -0,0 +1,19 @@
+package eu.functionizer.functionizertestingtool.model;
+
+import org.junit.platform.engine.TestExecutionResult;
+
+public enum TestResultEnum {
+ SUCCESS,
+ PARTIAL_FAILURE,
+ IGNORED,
+ FAILURE;
+
+
+ public static TestResultEnum fromTestExecutionResult(TestExecutionResult result) {
+ if (result.getStatus() == TestExecutionResult.Status.SUCCESSFUL) {
+ return SUCCESS;
+ } else {
+ return FAILURE;
+ }
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestRunner.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestRunner.java
new file mode 100644
index 0000000000000000000000000000000000000000..89a62839cc439f9f9c74e2d84c59754f730871bd
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestRunner.java
@@ -0,0 +1,53 @@
+package eu.functionizer.functionizertestingtool.service;
+
+import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult;
+import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData;
+import eu.functionizer.functionizertestingtool.service.test.FunctionizerTestListener;
+import eu.functionizer.functionizertestingtool.service.test.ServerlessFunctionTestFactory;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import org.junit.platform.launcher.Launcher;
+import org.junit.platform.launcher.LauncherDiscoveryRequest;
+import org.junit.platform.launcher.TestPlan;
+import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
+import org.junit.platform.launcher.core.LauncherFactory;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+
+
+@Service
+@Slf4j
+@AllArgsConstructor(onConstructor = @__(@Autowired))
+public class TestRunner {
+
+ public FunctionizerTestResult runTests() {
+
+ log.debug("Initiating Test Launcher");
+ Launcher launcher = LauncherFactory.create();
+
+ log.debug("Collecting test classes");
+ LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
+ .selectors(selectClass(ServerlessFunctionTestFactory.class))
+ .build();
+
+ TestPlan testPlan = launcher.discover(request);
+
+ log.debug("Initiating Listener");
+
+ FunctionizerTestListener listener = new FunctionizerTestListener();
+
+ log.debug("Registering Listener");
+ launcher.registerTestExecutionListeners(listener);
+ launcher.execute(request);
+
+ log.info("Generating Report");
+ FunctionizerReportData report = listener.getReport();
+ log.info("Test run ended");
+ return report.getTestResult();
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AWSLambdaService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AWSLambdaService.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b2d7aa30b6be5db7586d711397910d543c6c72f
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AWSLambdaService.java
@@ -0,0 +1,146 @@
+package eu.functionizer.functionizertestingtool.service.provider;
+
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.lambda.AWSLambda;
+import com.amazonaws.services.lambda.AWSLambdaClientBuilder;
+import com.amazonaws.services.lambda.model.InvokeRequest;
+import com.amazonaws.services.lambda.model.InvokeResult;
+import eu.functionizer.functionizertestingtool.model.ReportEntryKey;
+import eu.functionizer.functionizertestingtool.service.test.Stage;
+import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration;
+import eu.passage.upperware.commons.model.testing.TestCase;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.DynamicTest;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+@Slf4j
+public class AWSLambdaService extends TestPreparationService {
+
+ public static AWSLambda buildClient(String user, String secret, String location) {
+ BasicAWSCredentials credentials = new BasicAWSCredentials(user, secret);
+
+ return AWSLambdaClientBuilder.standard()
+ .withCredentials(new AWSStaticCredentialsProvider(credentials))
+ .withRegion(Regions.fromName(location))
+ .build();
+ }
+
+ public static void prepareAWSLambdaTests(
+ List functionNode,
+ FunctionTestConfiguration configuration,
+ String stackId,
+ String deployedName,
+ String user,
+ String secret,
+ String location
+ ) {
+ AWSLambda awsLambdaClient;
+
+ log.debug("Building AWS Lambda Function name");
+ String awsFunctionName = String.join(
+ "-",
+ stackId,
+ deployedName
+ );
+
+ log.debug("Building AWS Lambda client");
+ try {
+ awsLambdaClient = AWSLambdaService.buildClient(user, secret, location);
+ } catch (Exception e) {
+ failDynamicNode(
+ configuration.getFunctionName(),
+ Stage.BUILD_AWS_LAMBDA_CLIENT,
+ e.getMessage()
+ );
+ ignoreTestCases(functionNode, configuration);
+ return;
+ }
+
+ List awsTests = configuration.getTestCases()
+ .stream()
+ .map(testCase -> AWSLambdaService.createAWSLambdaTest(
+ awsLambdaClient,
+ configuration.getFunctionName(),
+ awsFunctionName,
+ testCase
+ ))
+ .collect(Collectors.toList());
+ functionNode.addAll(awsTests);
+ }
+
+ public static DynamicTest createAWSLambdaTest(
+ AWSLambda awsLambdaClient,
+ String functionName,
+ String awsLambdaFunctionName,
+ TestCase testCase
+ ) {
+ log.debug(
+ "Creating test case: event={}, expected output={}",
+ testCase.getEvent(),
+ testCase.getExpectedOutput()
+ );
+
+ String displayName = createTestCaseDisplayName(
+ functionName,
+ testCase.getEvent(),
+ testCase.getExpectedOutput()
+ );
+
+ Map reportEntry = createReportEntry(displayName);
+ reportEntry.put(ReportEntryKey.EVENT, testCase.getEvent());
+ reportEntry.put(ReportEntryKey.EXPECTED_OUTPUT, testCase.getExpectedOutput());
+ testReporter.publishEntry(reportEntry);
+
+ return dynamicTest(
+ displayName,
+ () -> executeAWSLambdaTestCase(
+ awsLambdaClient,
+ awsLambdaFunctionName,
+ testCase.getEvent(),
+ testCase.getExpectedOutput()
+ )
+ );
+ }
+
+ public static void executeAWSLambdaTestCase(
+ AWSLambda awsLambda,
+ String functionName,
+ String event,
+ String expectedOutput
+ ) {
+ InvokeRequest invokeRequest = new InvokeRequest()
+ .withFunctionName(functionName)
+ .withPayload(event);
+
+ try {
+ InvokeResult result = awsLambda.invoke(invokeRequest);
+ String resultString = new String(result.getPayload().array(), StandardCharsets.UTF_8);
+ if (Arrays.asList(200, 202, 204).contains(result.getStatusCode())) {
+ assertEquals(expectedOutput, resultString);
+ } else {
+ fail(String.format(
+ "Received status code %s. Reason: %s",
+ result.getStatusCode(),
+ resultString
+ ));
+ }
+ } catch (Exception e) {
+ fail(String.format(
+ "An exception while fetching response from AWS Lambda Client occurred. Cause: %s",
+ e.getMessage()
+ ));
+ }
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AzureFunctionsService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AzureFunctionsService.java
new file mode 100644
index 0000000000000000000000000000000000000000..34453b19876af9223098d2f30aea4483ec4377fb
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AzureFunctionsService.java
@@ -0,0 +1,200 @@
+package eu.functionizer.functionizertestingtool.service.provider;
+
+import com.microsoft.azure.AzureEnvironment;
+import com.microsoft.azure.credentials.ApplicationTokenCredentials;
+import com.microsoft.azure.management.Azure;
+import com.microsoft.azure.management.appservice.FunctionApp;
+import eu.functionizer.functionizertestingtool.model.ReportEntryKey;
+import eu.functionizer.functionizertestingtool.service.test.ServerlessFunctionTestFactory;
+import eu.functionizer.functionizertestingtool.service.test.Stage;
+import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration;
+import eu.passage.upperware.commons.model.testing.TestCase;
+import io.github.cloudiator.rest.model.Function;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestReporter;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+@Slf4j
+public class AzureFunctionsService extends TestPreparationService {
+ private final static String ENDPOINT_STATIC = "azurewebsites.net/api";
+ private final static String CLIENT_TENANT_DELIMITER = ":";
+
+ private static String buildFunctionUrl(String appName, String functionName) {
+ return String.format("http://%s.%s/%s", appName, ENDPOINT_STATIC, functionName);
+ }
+
+ private static String buildResourceGroupName(String stackId) {
+ return stackId + "group";
+ }
+
+ private static Azure buildAzureClient(String user, String secret) throws IOException {
+ int colonIndex = user.lastIndexOf(CLIENT_TENANT_DELIMITER);
+ String clientId = user.substring(0, colonIndex);
+ String tenantId = user.substring(colonIndex + 1);
+
+ ApplicationTokenCredentials credentials = new ApplicationTokenCredentials(
+ clientId,
+ tenantId,
+ secret,
+ AzureEnvironment.AZURE
+ );
+
+ return Azure.authenticate(credentials).withDefaultSubscription();
+ }
+
+ private static String getFunctionKey(Azure azureClient, String stackId) {
+ String resourceGroup = buildResourceGroupName(stackId);
+
+ FunctionApp functionApp = azureClient
+ .appServices()
+ .functionApps()
+ .getByResourceGroup(resourceGroup, stackId);
+
+ return functionApp.getMasterKey();
+ }
+
+ private static String invokeFunction(String url, String functionKey, String input) throws Exception {
+
+ RestTemplate restTemplate = new RestTemplate();
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+ headers.set("x-functions-key", functionKey);
+
+
+ HttpEntity entity = new HttpEntity<>(input, headers);
+ ResponseEntity response = restTemplate.postForEntity(
+ url,
+ entity,
+ String.class
+ );
+ if (response.getStatusCode() == HttpStatus.OK) {
+ return response.getBody();
+ } else {
+ throw new Exception("Not 200 response");
+ }
+ }
+
+ public static void prepareAzureTests(
+ List functionNode,
+ FunctionTestConfiguration configuration,
+ String stackId,
+ String user,
+ String secret
+ ) {
+ Azure azureClient;
+ String functionKey;
+
+ Stage stage = Stage.BUILD_AZURE_CLIENT;
+ try {
+ log.debug("Building Azure client");
+ azureClient = AzureFunctionsService.buildAzureClient(user, secret);
+
+ log.debug("Fetching Function Key");
+ stage = Stage.GET_AZURE_FUNCTION_KEY;
+ functionKey = AzureFunctionsService.getFunctionKey(azureClient, stackId);
+ } catch (Exception e) {
+ failDynamicNode(
+ configuration.getFunctionName(),
+ stage,
+ e.getMessage()
+ );
+ ignoreTestCases(functionNode, configuration);
+ return;
+ }
+
+ log.debug("Building Function URL");
+ String azureFunctionEndpoint = AzureFunctionsService.buildFunctionUrl(
+ stackId,
+ configuration.getTriggerPath()
+ );
+ log.info("Function URL = {}", azureFunctionEndpoint);
+
+ List azureTests = configuration
+ .getTestCases()
+ .stream()
+ .map(testCase -> AzureFunctionsService.createAzureTest(
+ configuration.getFunctionName(),
+ azureFunctionEndpoint,
+ functionKey,
+ testCase,
+ testReporter
+ ))
+ .collect(Collectors.toList());
+
+ functionNode.addAll(azureTests);
+ }
+
+ private static DynamicTest createAzureTest(
+ String functionName,
+ String azureFunctionEndpoint,
+ String functionKey,
+ TestCase testCase,
+ TestReporter testReporter
+ ) {
+ log.debug(
+ "Creating test case: event={}, expected output={}",
+ testCase.getEvent(),
+ testCase.getExpectedOutput()
+ );
+ String displayName = createTestCaseDisplayName(
+ functionName,
+ testCase.getEvent(),
+ testCase.getExpectedOutput()
+ );
+
+ Map reportEntry = createReportEntry(displayName);
+ reportEntry.put(ReportEntryKey.EVENT, testCase.getEvent());
+ reportEntry.put(ReportEntryKey.EXPECTED_OUTPUT, testCase.getExpectedOutput());
+ testReporter.publishEntry(reportEntry);
+
+ return dynamicTest(
+ displayName,
+ () -> executeAzureTestCase(
+ azureFunctionEndpoint,
+ functionKey,
+ testCase.getEvent(),
+ testCase.getExpectedOutput()
+ )
+ );
+ }
+
+ private static void executeAzureTestCase(
+ String endpoint,
+ String functionKey,
+ String event,
+ String expectedOutput
+ ) {
+ Stage stage = Stage.TEST_EXECUTION;
+ try {
+ String response = AzureFunctionsService.invokeFunction(
+ endpoint,
+ functionKey,
+ event
+ );
+ assertEquals(expectedOutput, response);
+ } catch (Exception e) {
+ fail(String.format(
+ "An exception while fetching response from AWS Lambda Client occurred. Cause: %s",
+ e.getMessage()
+ ));
+ }
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/TestPreparationService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/TestPreparationService.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e7e26967acb159882bf66015e7dad6f3bc36a56
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/TestPreparationService.java
@@ -0,0 +1,69 @@
+package eu.functionizer.functionizertestingtool.service.provider;
+
+import eu.functionizer.functionizertestingtool.model.ReportEntryKey;
+import eu.functionizer.functionizertestingtool.service.test.Stage;
+import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.TestReporter;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+@Slf4j
+public class TestPreparationService {
+
+ public static TestReporter testReporter;
+
+ static String createTestCaseDisplayName(
+ String functionName,
+ String input,
+ String expectedOutput
+ ) {
+ return String.format(
+ "Function %s invoked with %s should return %s",
+ functionName,
+ input,
+ expectedOutput
+ );
+ }
+
+ public static Map createReportEntry(String displayName) {
+ Map reportEntry = new HashMap<>();
+ reportEntry.put("displayName", displayName);
+ return reportEntry;
+ }
+
+ public static void failDynamicNode(String displayName, Stage stage, String cause) {
+ log.info(
+ "Dynamic Node '{}' construction failed at the stage {}. Cause: {}",
+ displayName,
+ stage.getName(),
+ cause
+ );
+ Map reportEntry = createReportEntry(displayName);
+ reportEntry.put(ReportEntryKey.STAGE, stage.getName());
+ reportEntry.put(ReportEntryKey.FAILURE_CAUSE, cause);
+ testReporter.publishEntry(reportEntry);
+ }
+
+ public static void ignoreTestCases(List functionNode, FunctionTestConfiguration configuration) {
+ configuration.getTestCases().forEach(testCase -> {
+ String testCaseDisplayName = createTestCaseDisplayName(
+ configuration.getFunctionName(),
+ testCase.getEvent(),
+ testCase.getExpectedOutput()
+ );
+ Map reportEntry = createReportEntry(testCaseDisplayName);
+ reportEntry.put(ReportEntryKey.EVENT, testCase.getEvent());
+ reportEntry.put(ReportEntryKey.EXPECTED_OUTPUT, testCase.getExpectedOutput());
+ reportEntry.put(ReportEntryKey.IGNORED, "true");
+ testReporter.publishEntry(reportEntry);
+ functionNode.add(dynamicTest(testCaseDisplayName, () -> fail("Test case ignored")));
+ });
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerReportData.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerReportData.java
new file mode 100644
index 0000000000000000000000000000000000000000..471105000b852e6db4de7b9778a74258971ba265
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerReportData.java
@@ -0,0 +1,217 @@
+package eu.functionizer.functionizertestingtool.service.test;
+
+import eu.functionizer.functionizertestingtool.model.*;
+import org.junit.ComparisonFailure;
+import org.junit.platform.commons.util.ExceptionUtils;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.engine.reporting.ReportEntry;
+import org.junit.platform.launcher.TestIdentifier;
+import org.junit.platform.launcher.TestPlan;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+
+import static eu.functionizer.functionizertestingtool.service.test.ServerlessFunctionTestFactory.ALL_TESTS_DISPLAY_NAME;
+import static java.util.Collections.emptyMap;
+import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED;
+import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL;
+
+public class FunctionizerReportData {
+ private static final int MILLIS_PER_SECOND = 1000;
+
+ public long timeFinished;
+ private List failures = new ArrayList<>();
+
+ private FunctionizerTestResult testResult;
+
+
+ private final Map finishedTests = new ConcurrentHashMap<>();
+ private final Map skippedTests = new ConcurrentHashMap<>();
+ private final Map startInstants = new ConcurrentHashMap<>();
+ private final Map endInstants = new ConcurrentHashMap<>();
+
+ private TestIdentifier root;
+ private final Map> reportEntries = new ConcurrentHashMap<>();
+
+ private final TestPlan testPlan;
+ private final Clock clock;
+
+ FunctionizerReportData(TestPlan testPlan, Clock clock) {
+ this.testPlan = testPlan;
+ this.clock = clock;
+
+ }
+
+ void markSkipped(TestIdentifier testIdentifier, String reason) {
+ this.skippedTests.put(testIdentifier, reason == null ? "" : reason);
+ }
+
+ void markStarted(TestIdentifier testIdentifier) {
+ this.startInstants.put(testIdentifier, this.clock.instant());
+ }
+
+ void markFinished(TestIdentifier testIdentifier, TestExecutionResult result) {
+ this.endInstants.put(testIdentifier, this.clock.instant());
+ if (result.getStatus() == ABORTED) {
+ String reason = result
+ .getThrowable()
+ .map(ExceptionUtils::readStackTrace)
+ .orElse("");
+ this.skippedTests.put(testIdentifier, reason);
+ }
+ else {
+ this.finishedTests.put(testIdentifier, result);
+ }
+ }
+
+ void addReportEntry(TestIdentifier testIdentifier, ReportEntry entry) {
+ // Note: we get the actual test display name from the report entry,
+ // because dynamic test report entries do not recognize their display names properly
+
+ if (entry.getKeyValuePairs().containsKey(ReportEntryKey.ROOT)) {
+ this.root = testIdentifier;
+ }
+
+ String actualTestDisplayName = entry.getKeyValuePairs().get("displayName");
+ if (actualTestDisplayName != null) {
+ Map report = this.reportEntries.computeIfAbsent(
+ actualTestDisplayName,
+ key -> new HashMap<>()
+ );
+ entry.getKeyValuePairs().forEach(report::put);
+ }
+ }
+
+ void addFailure(TestIdentifier testIdentifier, Throwable throwable) {
+ failures.add(new Failure(testIdentifier.getDisplayName(), throwable.getMessage()));
+ }
+
+ public static class Failure {
+ String displayName;
+ String cause;
+
+ public Failure(
+ String displayName,
+ String cause
+ ) {
+ this.displayName = displayName;
+ this.cause = cause;
+ }
+ }
+
+ TestExecutionResult getResult(TestIdentifier testIdentifier) {
+ if (this.finishedTests.containsKey(testIdentifier)) {
+ return this.finishedTests.get(testIdentifier);
+ }
+ Optional parent = this.testPlan.getParent(testIdentifier);
+ if (parent.isPresent()) {
+ Optional ancestor = findAncestor(
+ parent.get(),
+ this.finishedTests::containsKey
+ );
+ if (ancestor.isPresent()) {
+ TestExecutionResult result = this.finishedTests.get(ancestor.get());
+ if (result.getStatus() != SUCCESSFUL) {
+ return result;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ double getDurationInSeconds(TestIdentifier testIdentifier) {
+ Instant startInstant = this.startInstants.getOrDefault(testIdentifier, Instant.EPOCH);
+ Instant endInstant = this.endInstants.getOrDefault(testIdentifier, startInstant);
+ return Duration.between(startInstant, endInstant).toMillis() / (double) MILLIS_PER_SECOND;
+ }
+
+ private Optional findAncestor(
+ TestIdentifier testIdentifier,
+ Predicate predicate
+ ) {
+ Optional current = Optional.of(testIdentifier);
+ while (current.isPresent()) {
+ if (predicate.test(current.get())) {
+ return current;
+ }
+ current = this.testPlan.getParent(current.get());
+ }
+ return Optional.empty();
+ }
+
+ public FunctionizerTestResult getTestResult() {
+ return this.testResult;
+ }
+
+ public void prepareTestResult() {
+ this.testResult = new FunctionizerTestResult();
+ this.testResult.setDuration(getDurationInSeconds(this.root));
+
+ Map testReportEntry = this.reportEntries.getOrDefault(
+ ALL_TESTS_DISPLAY_NAME,
+ emptyMap()
+ );
+ if (testReportEntry.containsKey(ReportEntryKey.STAGE)) {
+ this.testResult.setMessage(testReportEntry.get(ReportEntryKey.FAILURE_CAUSE));
+ this.testResult.setFailedAtStage(testReportEntry.get(ReportEntryKey.STAGE));
+ this.testResult.setTestsRunResult(TestResultEnum.FAILURE);
+ return;
+ }
+
+ Set functionNodes = testPlan.getChildren(this.root);
+ for (TestIdentifier functionNode : functionNodes) {
+ FunctionTestResult functionTestResult = new FunctionTestResult(
+ functionNode.getDisplayName()
+ );
+ functionTestResult.setDuration(getDurationInSeconds(functionNode));
+ Map functionReportEntry = this.reportEntries.getOrDefault(
+ functionNode.getDisplayName(),
+ emptyMap()
+ );
+ if (functionReportEntry.containsKey(ReportEntryKey.STAGE)) {
+ functionTestResult.setFailedAtStage(functionReportEntry.get(ReportEntryKey.STAGE));
+ functionTestResult.setMessage(functionReportEntry.get(ReportEntryKey.FAILURE_CAUSE));
+ functionTestResult.setOverallResult(TestResultEnum.FAILURE);
+ }
+ Set testCases = testPlan.getChildren(functionNode);
+
+ for(TestIdentifier testCase : testCases) {
+ TestCaseResult testCaseResult = new TestCaseResult();
+ TestExecutionResult result = getResult(testCase);
+ testCaseResult.setDuration(getDurationInSeconds(testCase));
+ testCaseResult.setResult(TestResultEnum.fromTestExecutionResult(result));
+
+ Map reportEntry = this.reportEntries.getOrDefault(
+ testCase.getDisplayName(),
+ emptyMap()
+ );
+ if (reportEntry.containsKey(ReportEntryKey.IGNORED)) {
+ testCaseResult.setResult(TestResultEnum.IGNORED);
+ }
+ testCaseResult.setEvent(reportEntry.get(ReportEntryKey.EVENT));
+ testCaseResult.setExpectedOutput(reportEntry.get(ReportEntryKey.EXPECTED_OUTPUT));
+ testCaseResult.setActualOutput(reportEntry.get(ReportEntryKey.EXPECTED_OUTPUT));
+
+ result.getThrowable().ifPresent(error -> {
+ testCaseResult.setMessage(error.getMessage());
+ if (error instanceof ComparisonFailure) {
+ ComparisonFailure comparisonFailure = (ComparisonFailure) error;
+ testCaseResult.setExpectedOutput(comparisonFailure.getExpected());
+ testCaseResult.setActualOutput(comparisonFailure.getActual());
+ } else {
+ testCaseResult.setActualOutput(null);
+ }
+ });
+ functionTestResult.addTestCaseResult(testCaseResult);
+ }
+
+ this.testResult.addFunctionTestResult(functionTestResult);
+ }
+ this.testResult.setTestsRunResult(TestResultEnum.SUCCESS);
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestListener.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..a93bec0a50b9e4f3167bb58be77fa5c2be9cb95e
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestListener.java
@@ -0,0 +1,83 @@
+package eu.functionizer.functionizertestingtool.service.test;
+
+import org.junit.platform.commons.util.PreconditionViolationException;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.engine.reporting.ReportEntry;
+import org.junit.platform.launcher.TestExecutionListener;
+import org.junit.platform.launcher.TestIdentifier;
+import org.junit.platform.launcher.TestPlan;
+
+import java.time.Clock;
+
+public class FunctionizerTestListener implements TestExecutionListener {
+
+ private final Clock clock;
+
+ private FunctionizerReportData reportData;
+
+ public FunctionizerTestListener() {
+ this(Clock.systemDefaultZone());
+ }
+
+ private FunctionizerTestListener(Clock clock) {
+ this.clock = clock;
+ }
+
+ public FunctionizerReportData getReport() {
+ return this.reportData;
+ }
+
+ @Override
+ public void testPlanExecutionStarted(TestPlan testPlan) {
+ this.reportData = new FunctionizerReportData(testPlan, clock);
+ }
+
+ @Override
+ public void testPlanExecutionFinished(TestPlan testPlan) {
+ this.reportData.timeFinished = System.currentTimeMillis();
+ this.reportData.prepareTestResult();
+ }
+
+ @Override
+ public void dynamicTestRegistered(TestIdentifier testIdentifier) {
+ }
+
+ @Override
+ public void executionSkipped(TestIdentifier testIdentifier, String reason) {
+ this.reportData.markSkipped(testIdentifier, reason);
+ }
+
+ @Override
+ public void executionStarted(TestIdentifier testIdentifier) {
+ this.reportData.markStarted(testIdentifier);
+ }
+
+ @Override
+ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) {
+ this.reportData.addReportEntry(testIdentifier, entry);
+ }
+
+ @Override
+ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) {
+
+ switch (testExecutionResult.getStatus()) {
+
+ case SUCCESSFUL:
+
+ case ABORTED: {
+ break;
+ }
+
+ case FAILED: {
+ testExecutionResult.getThrowable().ifPresent(
+ throwable -> this.reportData.addFailure(testIdentifier, throwable));
+ break;
+ }
+
+ default:
+ throw new PreconditionViolationException(
+ "Unsupported execution status:" + testExecutionResult.getStatus());
+ }
+ this.reportData.markFinished(testIdentifier, testExecutionResult);
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/ServerlessFunctionTestFactory.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/ServerlessFunctionTestFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..a84df2e836cf8f335789253a41386e89c3f29fff
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/ServerlessFunctionTestFactory.java
@@ -0,0 +1,410 @@
+package eu.functionizer.functionizertestingtool.service.test;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import eu.functionizer.functionizertestingtool.model.CloudiatorData;
+import eu.functionizer.functionizertestingtool.service.provider.TestPreparationService;
+import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration;
+import eu.functionizer.functionizertestingtool.model.ReportEntryKey;
+import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoader;
+import eu.passage.upperware.commons.cloudiator.CloudiatorApi;
+import eu.passage.upperware.commons.cloudiator.CloudiatorClientApi;
+import eu.passage.upperware.commons.cloudiator.QueueInspector;
+import eu.passage.upperware.commons.model.provider.CloudDefinition;
+import eu.passage.upperware.commons.model.provider.Credential;
+import eu.passage.upperware.commons.model.provider.Provider;
+import eu.passage.upperware.commons.service.provider.ProviderIdCreatorService;
+import eu.passage.upperware.commons.service.provider.ProviderService;
+import eu.passage.upperware.commons.service.provider.ProviderValidationService;
+import eu.passage.upperware.commons.service.store.SecureStoreService;
+import eu.passage.upperware.commons.service.yaml.YamlDataService;
+import eu.passage.upperware.commons.cloudiator.CloudiatorProperties;
+
+import io.github.cloudiator.rest.ApiClient;
+import io.github.cloudiator.rest.ApiException;
+import io.github.cloudiator.rest.api.*;
+
+import io.github.cloudiator.rest.model.*;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.*;
+
+import static eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService.prepareAWSLambdaTests;
+import static eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService.prepareAzureTests;
+import static eu.functionizer.functionizertestingtool.service.provider.TestPreparationService.*;
+
+@Slf4j
+public class ServerlessFunctionTestFactory {
+
+ private static final String CLOUDIATOR_CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR")
+ + "/eu.melodic.cloudiator-client.properties";
+
+ public static final String ALL_TESTS_DISPLAY_NAME = "All tests";
+
+ private static Map providerMap;
+
+ private static CloudiatorApi cloudiatorApi;
+ private static MatchmakingApi matchmakingApi;
+ private static ProviderService providerService;
+ private static Stage stage = null;
+
+ TestReporter testReporter;
+
+ @BeforeAll
+ static void setUp() throws IOException {
+
+ Properties properties = new Properties();
+ properties.load(new FileInputStream(CLOUDIATOR_CONFIG_FILE_PATH));
+
+ CloudiatorProperties.Cloudiator cloudiator = new CloudiatorProperties.Cloudiator();
+ cloudiator.setUrl(properties.getProperty("cloudiator.url"));
+ cloudiator.setApiKey(properties.getProperty("cloudiator.apiKey"));
+ cloudiator.setHttpReadTimeout(
+ Integer.parseInt(properties.getProperty("cloudiator.httpReadTimeout"))
+ );
+
+ CloudiatorProperties cloudiatorProperties = new CloudiatorProperties();
+ cloudiatorProperties.setCloudiator(cloudiator);
+
+ ApiClient apiClient = new ApiClient();
+ apiClient.setBasePath(cloudiator.getUrl());
+ apiClient.setApiKey(cloudiator.getApiKey());
+ apiClient.setReadTimeout(cloudiator.getHttpReadTimeout());
+
+ QueueApi queueApi = new QueueApi(apiClient);
+
+ cloudiatorApi = new CloudiatorClientApi(
+ new CloudApi(apiClient),
+ new SecurityApi(apiClient),
+ new NodeApi(apiClient),
+ new ProcessApi(apiClient),
+ queueApi,
+ new JobApi(apiClient),
+ new MonitoringApi(apiClient),
+ new QueueInspector(queueApi, cloudiatorProperties)
+ );
+
+ matchmakingApi = new MatchmakingApi(apiClient);
+ providerService = new ProviderService(
+ new ProviderIdCreatorService(),
+ new ProviderValidationService(),
+ cloudiatorApi,
+ new SecureStoreService(cloudiatorApi),
+ new YamlDataService()
+ );
+
+ providerMap = new HashMap<>();
+ for (Provider provider : Provider.values()) {
+ providerMap.put(provider.value, provider);
+ }
+ }
+
+ @BeforeEach
+ private void init(TestReporter testReporter) {
+ this.testReporter = testReporter;
+ }
+
+
+ @TestFactory
+ @DisplayName(ALL_TESTS_DISPLAY_NAME)
+ public Collection dynamicTestNodes() {
+ log.info("Test Factory started");
+ stage = Stage.START;
+ TestPreparationService.testReporter = this.testReporter;
+
+ Map entry = createReportEntry(ALL_TESTS_DISPLAY_NAME);
+ entry.put(ReportEntryKey.ROOT, "true");
+ testReporter.publishEntry(entry);
+
+ List dynamicNodes = new ArrayList<>();
+ List functionTests;
+
+ log.info("Loading test configuration");
+ stage = Stage.LOAD_CONFIG;
+ try {
+ functionTests = TestConfigurationLoader
+ .loadTestConfiguration()
+ .getTests();
+ } catch (Exception e) {
+ failDynamicNode(ALL_TESTS_DISPLAY_NAME, stage, e.getMessage());
+ return dynamicNodes;
+ }
+
+ CloudiatorData cloudiatorData;
+
+ log.info("Fetching data from Cloudiator");
+ stage = Stage.FETCH_CLOUDIATOR;
+ try {
+ cloudiatorData = fetchCloudiatorData();
+ } catch (Exception e) {
+ testReporter.publishEntry("result", "fail");
+ failDynamicNode(ALL_TESTS_DISPLAY_NAME, stage, e.getMessage());
+ return dynamicNodes;
+ }
+ if (cloudiatorData == null) {
+ testReporter.publishEntry("result", "fail");
+ failDynamicNode(
+ ALL_TESTS_DISPLAY_NAME, stage,
+ "There are no Functions on the deployed Function list received from Cloudiator."
+ );
+ return dynamicNodes;
+ }
+
+ log.info("Cloudiator data fetched successfully");
+
+ for (FunctionTestConfiguration functionTestConfiguration : functionTests) {
+ String functionConfigurationName = functionTestConfiguration.getFunctionName();
+
+ log.info("Gathering data on Function {}", functionConfigurationName);
+ stage = Stage.GATHER_FUNCTION_DATA;
+
+ log.debug("Looking up Function in the deployed Function list");
+ Function function = cloudiatorData
+ .getDeployedFunctions()
+ .get(functionConfigurationName.toLowerCase());
+
+ List functionNode;
+
+ if (function == null) {
+ functionNode = new ArrayList<>();
+ failDynamicNode(
+ functionConfigurationName,
+ stage,
+ "Not found in the deployed Function list"
+ );
+ ignoreTestCases(functionNode, functionTestConfiguration);
+ } else {
+
+ String functionDeployedName = cloudiatorData
+ .getFunctionDeployNames()
+ .get(functionConfigurationName);
+
+ NodeCandidate nodeCandidate = cloudiatorData
+ .getNodeCandidates()
+ .get(function.getId());
+
+ log.debug("Collecting test cases");
+ functionNode = prepareFunctionNode(
+ functionTestConfiguration,
+ nodeCandidate,
+ cloudiatorData.getUserSecrets(),
+ functionDeployedName,
+ function
+ );
+ }
+
+ dynamicNodes.add(
+ DynamicContainer.dynamicContainer(
+ functionConfigurationName,
+ functionNode
+ )
+ );
+ }
+ stage = Stage.END;
+ return dynamicNodes;
+ }
+
+ private CloudiatorData fetchCloudiatorData(
+ ) throws ApiException {
+
+ log.debug("Fetching Functions from Cloudiator");
+ Map functions = getFunctionsMap();
+ if (functions.size() == 0) {
+ return null;
+ }
+
+ log.debug("Fetching Function Names from Cloudiator");
+ Map functionDeployNames = getFunctionDeployNames();
+
+ log.debug("Fetching Node Candidates from Cloudiator");
+ Map nodeCandidates = getFaasNodeCandidates();
+
+ log.debug("Fetching Cloud Provider Credentials");
+ stage = Stage.FETCH_CREDENTIALS;
+ Map userSecrets = getCredentials();
+
+ return new CloudiatorData(
+ functions,
+ functionDeployNames,
+ nodeCandidates,
+ userSecrets
+ );
+ }
+
+ private Map getFunctionsMap() {
+ List functions = cloudiatorApi.getFunctionList();
+ return functions
+ .stream()
+ .filter(function -> !Objects.requireNonNull(function.getStackId()).isEmpty())
+ .collect(Collectors.toMap(
+ function -> Objects.requireNonNull(function.getStackId()).split("-")[0],
+ function -> function
+ ));
+ }
+
+ private Map getFunctionDeployNames() {
+ Stream faasTasks = getFaasTasks();
+
+ log.debug("Extracting Function Names");
+ return faasTasks
+ .collect(Collectors.toMap(
+ Task::getName,
+ ServerlessFunctionTestFactory::getTaskInterfaceFunctionName
+ ));
+ }
+
+ private Stream getFaasTasks() {
+ log.debug("Fetching Cloudiator Jobs");
+ List jobs = cloudiatorApi.getJobList();
+
+ log.debug("Extracting FaaS tasks from jobs");
+ return jobs
+ .stream()
+ .map(Job::getTasks)
+ .filter(Objects::nonNull)
+ .flatMap(List::stream)
+ .filter(task -> Objects.nonNull(task)
+ && Objects.nonNull(task.getInterfaces())
+ && Objects.requireNonNull(task.getInterfaces())
+ .stream()
+ .findFirst()
+ .filter(anInterface -> anInterface instanceof FaasInterface)
+ .isPresent()
+ );
+ }
+
+ private static String getTaskInterfaceFunctionName(Task task) {
+ Optional taskInterface = Objects
+ .requireNonNull(task.getInterfaces())
+ .stream()
+ .findFirst();
+ if (taskInterface.isPresent()) {
+ FaasInterface faasInterface = (FaasInterface) taskInterface.get();
+ return faasInterface.getFunctionName();
+ }
+ return "";
+ }
+
+ private Map getFaasNodeCandidates() throws ApiException {
+ log.debug("Fetching FaaS Nodes");
+ List faasNodes = cloudiatorApi.getFaasFromNodeList();
+
+ log.debug("Determining Node Candidates");
+ List nodeCandidateIds = faasNodes
+ .stream()
+ .map(Node::getNodeCandidate)
+ .collect(Collectors.toList());
+
+ Map nodeCandidates = getNodeCandidatesByIds(nodeCandidateIds)
+ .stream()
+ .collect(Collectors.toMap(NodeCandidate::getId, nodeCandidate -> nodeCandidate));
+
+ return faasNodes
+ .stream()
+ .collect(Collectors.toMap(
+ Node::getOriginId,
+ node -> nodeCandidates.get(node.getNodeCandidate())
+ ));
+ }
+
+ private List getNodeCandidatesByIds(List ids) throws ApiException {
+ log.debug("Fetching Node Candidates");
+ String constraint = String.format(
+ "nodes->forAll(n | Set{'%s'}->includes(n.id))",
+ String.join("','", ids)
+ );
+ return matchmakingApi.findNodeCandidates(
+ Collections.singletonList(new OclRequirement()
+ .constraint(constraint)
+ .type("OclRequirement")
+ )
+ );
+ }
+
+ private Map getCredentials() {
+ return providerService
+ .getCloudDefinitionsForAllProviders()
+ .stream()
+ .map(CloudDefinition::getCredential)
+ .collect(Collectors.toMap(Credential::getUser, Credential::getSecret));
+ }
+
+ private List prepareFunctionNode(
+ FunctionTestConfiguration functionTestConfiguration,
+ NodeCandidate nodeCandidate,
+ Map userSecrets,
+ String functionDeployedName,
+ Function function
+ ) {
+ log.info("Preparing Function Node");
+ List functionNode = new ArrayList<>();
+
+ Cloud cloud = nodeCandidate.getCloud();
+ if (cloud == null) {
+ log.info("Cloud not found. Omitting test cases");
+ failDynamicNode(
+ functionTestConfiguration.getFunctionName(),
+ Stage.GATHER_FUNCTION_DATA,"Cloud not found"
+ );
+ ignoreTestCases(functionNode, functionTestConfiguration);
+ return functionNode;
+ }
+
+ log.debug("Retrieving Cloud credentials");
+ String user = Objects.requireNonNull(nodeCandidate.getCloud())
+ .getCredential()
+ .getUser();
+ String secret = userSecrets.get(user);
+ if (user == null || secret == null) {
+ failDynamicNode
+ (functionTestConfiguration.getFunctionName(),
+ stage,
+ "Empty credentials."
+ );
+ ignoreTestCases(functionNode, functionTestConfiguration);
+ return functionNode;
+ }
+ Provider provider = providerMap.get(
+ nodeCandidate.getCloud().getApi().getProviderName()
+ );
+
+ switch (provider) {
+ case AWS_EC2:
+ String location = Objects.requireNonNull(nodeCandidate.getLocation()).getProviderId();
+
+ prepareAWSLambdaTests(
+ functionNode,
+ functionTestConfiguration,
+ function.getStackId(),
+ functionDeployedName,
+ user,
+ secret,
+ location
+ );
+ break;
+
+ case AZURE:
+ prepareAzureTests(
+ functionNode,
+ functionTestConfiguration,
+ function.getStackId(),
+ user,
+ secret
+ );
+ break;
+
+ default:
+ log.info(
+ "Function {} has been deployed on unsupported Cloud. Omitting",
+ functionDeployedName
+ );
+ ignoreTestCases(functionNode, functionTestConfiguration);
+ }
+ return functionNode;
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/Stage.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/Stage.java
new file mode 100644
index 0000000000000000000000000000000000000000..25a12ea988e9e327816f956ba1387fbb667bc46a
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/Stage.java
@@ -0,0 +1,25 @@
+package eu.functionizer.functionizertestingtool.service.test;
+
+public enum Stage {
+ START("TEST FACTORY INITIALIZATION"),
+ LOAD_CONFIG("LOAD TEST CONFIG FILE"),
+ FETCH_CLOUDIATOR("FETCH CLOUDIATOR DATA"),
+ FETCH_CREDENTIALS("FETCH CLOUD CREDENTIALS"),
+ GATHER_FUNCTION_DATA("GATHER FUNCTION CLOUD DATA"),
+ BUILD_AZURE_CLIENT("BUILD AZURE CLIENT"),
+ BUILD_AWS_LAMBDA_CLIENT("BUILD AWS LAMBDA CLIENT"),
+ GET_AZURE_FUNCTION_KEY("GET AZURE FUNCTION KEY"),
+ TEST_EXECUTION("TEST CASE EXECUTION"),
+ AWS_INVOKE("INVOKE AWS LAMBDA FUNCTION"),
+ END("COLLECTING TESTS FINISH");
+
+ private final String name;
+
+ Stage(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationLoader.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa6bfda4f2de44a859ac513c5a631a410e4a26ef
--- /dev/null
+++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationLoader.java
@@ -0,0 +1,67 @@
+package eu.functionizer.functionizertestingtool.service.yaml;
+
+
+import eu.passage.upperware.commons.model.testing.TestConfiguration;
+import eu.passage.upperware.commons.MelodicConstants;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.constructor.ConstructorException;
+
+import java.io.*;
+
+import static eu.passage.upperware.commons.service.testing.TestConfigurationValidationService.validate;
+
+@Service
+@Slf4j
+public class TestConfigurationLoader {
+
+ private final static String CONFIG_FILE_PATH = MelodicConstants.TEST_CONFIG_FILE_DIR + "/tests.yml";
+
+ public static TestConfiguration loadTestConfiguration() throws Exception {
+ Yaml yaml = new Yaml(new Constructor(TestConfiguration.class));
+ TestConfiguration configuration;
+
+ try (FileInputStream fileInputStream = new FileInputStream(new File(CONFIG_FILE_PATH))) {
+ configuration = yaml.load(fileInputStream);
+
+ } catch (FileNotFoundException e) {
+ String errorMessage = String.format(
+ "Test configuration file: %s is missing.",
+ CONFIG_FILE_PATH
+ );
+ log.error(errorMessage, e);
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage);
+ } catch (IOException e) {
+ String errorMessage = String.format(
+ "Problem by reading file with test configuration: %s",
+ CONFIG_FILE_PATH
+ );
+ log.error(errorMessage, e);
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage);
+ } catch (ConstructorException e) {
+ String errorMessage = String.format(
+ "The file has a bad format and could not be parsed: %s", e.getMessage()
+ );
+ log.error(errorMessage);
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage);
+ }
+ validate(configuration);
+ return configuration;
+ }
+
+
+ private void saveTestConfiguration(TestConfiguration testConfiguration) {
+ Yaml yaml = new Yaml();
+ FileWriter writer = null;
+ try {
+ writer = new FileWriter(CONFIG_FILE_PATH);
+ } catch (IOException e) {
+ log.error("Error by writing to file {} with test configuration: ", CONFIG_FILE_PATH, e);
+ }
+ yaml.dump(testConfiguration, writer);
+ }
+}
diff --git a/functionizer-testing-tool/src/main/resources/eu.functionizer.testingTool.properties b/functionizer-testing-tool/src/main/resources/eu.functionizer.testingTool.properties
new file mode 100644
index 0000000000000000000000000000000000000000..bfea1b24a0dd1816c036a3aa0ea89496c41f5585
--- /dev/null
+++ b/functionizer-testing-tool/src/main/resources/eu.functionizer.testingTool.properties
@@ -0,0 +1,2 @@
+#### Logback-config
+logging.config=file:${MELODIC_CONFIG_DIR}/logback-conf/logback-spring.xml
diff --git a/gui-backend/pom.xml b/gui-backend/pom.xml
index 4f9b10fb4494163568e674cd6f6af7fb8b7d5a43..ff85b6922ccd122d9a9a8a04fbcb974aaffa1158 100644
--- a/gui-backend/pom.xml
+++ b/gui-backend/pom.xml
@@ -61,13 +61,6 @@
8.0.13
-
-
- org.yaml
- snakeyaml
- 1.21
-
-
org.passay
passay
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/ApplicationContext.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/ApplicationContext.java
index fba45c546bfbc4b9a84769be58a539f39f4c3bde..7b77406c2f579b4028d3610bca0d80e4154e177c 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/ApplicationContext.java
+++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/ApplicationContext.java
@@ -3,7 +3,15 @@ package eu.melodic.upperware.guibackend;
import eu.paasage.upperware.security.authapi.properties.MelodicSecurityProperties;
import eu.paasage.upperware.security.authapi.token.JWTService;
import eu.paasage.upperware.security.authapi.token.JWTServiceImpl;
+import eu.passage.upperware.commons.cloudiator.CloudiatorApi;
+import eu.passage.upperware.commons.cloudiator.CloudiatorClientApi;
import eu.passage.upperware.commons.cloudiator.CloudiatorProperties;
+import eu.passage.upperware.commons.cloudiator.QueueInspector;
+import eu.passage.upperware.commons.service.provider.ProviderIdCreatorService;
+import eu.passage.upperware.commons.service.provider.ProviderService;
+import eu.passage.upperware.commons.service.provider.ProviderValidationService;
+import eu.passage.upperware.commons.service.store.SecureStoreService;
+import eu.passage.upperware.commons.service.yaml.YamlDataService;
import io.github.cloudiator.rest.ApiClient;
import io.github.cloudiator.rest.api.*;
import org.springframework.context.annotation.Bean;
@@ -68,8 +76,68 @@ public class ApplicationContext {
return new MonitoringApi(apiClient);
}
+ @Bean
+ public CloudiatorApi cloudiatorApi(
+ CloudApi cloudApi,
+ SecurityApi securityApi,
+ NodeApi nodeApi,
+ ProcessApi processApi,
+ QueueApi queueApi,
+ JobApi jobApi,
+ MonitoringApi monitoringApi,
+ QueueInspector queueInspector
+ ) {
+ return new CloudiatorClientApi(
+ cloudApi,
+ securityApi,
+ nodeApi,
+ processApi,
+ queueApi,
+ jobApi,
+ monitoringApi,
+ queueInspector
+ );
+ }
+
@Bean
public JWTService jWTService(MelodicSecurityProperties melodicSecurityProperties) {
return new JWTServiceImpl(melodicSecurityProperties);
}
+
+ @Bean
+ public ProviderIdCreatorService providerIdCreatorService() {
+ return new ProviderIdCreatorService();
+ }
+
+ @Bean
+ ProviderValidationService providerValidationService() {
+ return new ProviderValidationService();
+ }
+
+ @Bean
+ SecureStoreService secureStoreService(CloudiatorApi cloudiatorApi) {
+ return new SecureStoreService(cloudiatorApi);
+ }
+
+ @Bean
+ public YamlDataService yamlDataService() {
+ return new YamlDataService();
+ }
+
+ @Bean
+ public ProviderService providerService(
+ ProviderIdCreatorService providerIdCreatorService,
+ ProviderValidationService providerValidationService,
+ CloudiatorApi cloudiatorApi,
+ SecureStoreService secureStoreService,
+ YamlDataService yamlDataService
+ ) {
+ return new ProviderService(
+ providerIdCreatorService,
+ providerValidationService,
+ cloudiatorApi,
+ secureStoreService,
+ yamlDataService
+ );
+ }
}
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/commons/ServiceName.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/commons/ServiceName.java
index 3b182c4e5590c71ab2f6d077b42fc9733e4ad071..2c443f6a674f11529cf96d90f15a6fc084609bac 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/commons/ServiceName.java
+++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/commons/ServiceName.java
@@ -7,7 +7,8 @@ public enum ServiceName {
CAMUNDA("Camunda"),
JWT_SERVER("Authorization service"),
ADAPTER("Adapter"),
- METASOLVER("MetaSolver");
+ METASOLVER("MetaSolver"),
+ FUNCTIONIZER_TESTING_TOOL("Functionizer testing tool");
public final String name;
}
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/mule/deployment/CloudDefinitionRequest.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/mule/deployment/CloudDefinitionRequest.java
index eb0d67d9bedb8564a00d14c496785a9fe45e5cec..756b1ab25d5ff04028a5f01f4d5a91acb1a8997f 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/mule/deployment/CloudDefinitionRequest.java
+++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/mule/deployment/CloudDefinitionRequest.java
@@ -1,6 +1,6 @@
package eu.melodic.upperware.guibackend.communication.mule.deployment;
-import eu.melodic.upperware.guibackend.model.provider.CloudType;
+import eu.passage.upperware.commons.model.provider.CloudType;
import lombok.Builder;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolApi.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..d2bd68cff2608db29c10751a628fd91ece03ebb2
--- /dev/null
+++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolApi.java
@@ -0,0 +1,5 @@
+package eu.melodic.upperware.guibackend.communication.testingtool;
+
+public interface FunctionizerTestingToolApi {
+ Object runTests(String token);
+}
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolClientApi.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolClientApi.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d04ce47e74e5abe703028b991e257b48aea769b
--- /dev/null
+++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolClientApi.java
@@ -0,0 +1,40 @@
+package eu.melodic.upperware.guibackend.communication.testingtool;
+
+import eu.melodic.upperware.guibackend.communication.commons.RestCommunicationService;
+import eu.melodic.upperware.guibackend.communication.commons.ServiceName;
+import eu.melodic.upperware.guibackend.properties.GuiBackendProperties;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+@Service
+
+public class FunctionizerTestingToolClientApi extends RestCommunicationService implements FunctionizerTestingToolApi {
+
+ private GuiBackendProperties guiBackendProperties;
+
+ public FunctionizerTestingToolClientApi(RestTemplate restTemplate, GuiBackendProperties guiBackendProperties) {
+ super(restTemplate);
+ this.guiBackendProperties = guiBackendProperties;
+ }
+
+ @Override
+ public Object runTests(String token) {
+ String requestUrl = guiBackendProperties.getFunctionizerTestingTool().getUrl() + "/test";
+ ParameterizedTypeReference
+
+ org.yaml
+ snakeyaml
+ 1.21
+
+
eu.melodic.cdo
client
repackaged
+
+ io.github.cloudiator.client
+ java-rest
+ compile
+
+
org.apache.commons
commons-collections4
@@ -59,6 +71,20 @@
commons-lang3
+
+ javax.ws.rs
+ javax.ws.rs-api
+ 2.1
+ compile
+
+
+
+ org.codehaus.jackson
+ jackson-mapper-asl
+ 1.9.13
+ compile
+
+
diff --git a/melodic-commons/src/main/java/eu/passage/upperware/commons/MelodicConstants.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/MelodicConstants.java
index e17de0af93a257ba6803e4584ab3bc98370be872..8ff1e2319227430e8f3e4905765617cf08ad9c48 100644
--- a/melodic-commons/src/main/java/eu/passage/upperware/commons/MelodicConstants.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/MelodicConstants.java
@@ -14,6 +14,7 @@ public class MelodicConstants {
public static final String TOMCAT_TEM_DIR= ".."+File.separator+"temp"+File.separator;
public static final String TOMCAT_GENERATION_DIR= TOMCAT_TEM_DIR+"paasage"+File.separator+"configurations"+File.separator;
public static final String TOMCAT_ALT_GENERATION_TEMP_DIR= TOMCAT_ALT_TEM_DIR+"paasage"+File.separator+"configurations"+File.separator;
+ public static final String TEST_CONFIG_FILE_DIR = System.getenv("MELODIC_CONFIG_DIR") + File.separator + "tests";
private MelodicConstants() {}
}
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/CloudiatorApi.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/CloudiatorApi.java
similarity index 93%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/CloudiatorApi.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/CloudiatorApi.java
index 38771a21076a0d7396743b3e94ed374a739a964c..28ac65d24e75d2fcc7038f14dbf46ae7876865e9 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/CloudiatorApi.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/CloudiatorApi.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.communication.cloudiator;
+package eu.passage.upperware.commons.cloudiator;
import io.github.cloudiator.rest.model.*;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/CloudiatorClientApi.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/CloudiatorClientApi.java
similarity index 98%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/CloudiatorClientApi.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/CloudiatorClientApi.java
index 30b2e4cbe32b8e1facf0282d598e1a0a9d3094a9..e4fda2b91fb10c03f463eb8cd2cbceae3576fe18 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/CloudiatorClientApi.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/CloudiatorClientApi.java
@@ -1,6 +1,6 @@
-package eu.melodic.upperware.guibackend.communication.cloudiator;
+package eu.passage.upperware.commons.cloudiator;
-import eu.melodic.upperware.guibackend.exception.SecureVariableNotFoundException;
+import eu.passage.upperware.commons.exception.SecureVariableNotFoundException;
import io.github.cloudiator.rest.ApiException;
import io.github.cloudiator.rest.api.*;
import io.github.cloudiator.rest.model.*;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/QueueInspector.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/QueueInspector.java
similarity index 97%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/QueueInspector.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/QueueInspector.java
index aed5bd2cd9bb2892418e45fee4127a809c5c43c4..2fad9481d4abf65d94899e39ae0039582b64eb5f 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/cloudiator/QueueInspector.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/cloudiator/QueueInspector.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.communication.cloudiator;
+package eu.passage.upperware.commons.cloudiator;
import eu.passage.upperware.commons.cloudiator.CloudiatorProperties;
import io.github.cloudiator.rest.ApiException;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/CloudDefinitionNotFoundException.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/exception/CloudDefinitionNotFoundException.java
similarity index 88%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/CloudDefinitionNotFoundException.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/exception/CloudDefinitionNotFoundException.java
index 061cf8d04de0a9d5adf4c16d37f8247ce1ffda69..e5989c45eb1e2ffe76ec1f7633b9fad948da3270 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/CloudDefinitionNotFoundException.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/exception/CloudDefinitionNotFoundException.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.exception;
+package eu.passage.upperware.commons.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/SecureVariableNotFoundException.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/exception/SecureVariableNotFoundException.java
similarity index 88%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/SecureVariableNotFoundException.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/exception/SecureVariableNotFoundException.java
index c8a0d0239bcc43c7fa8f2e2fc3cead30c16d1cc1..76b3b349eda400a85f5f9bba4ff9352e94cfab9e 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/SecureVariableNotFoundException.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/exception/SecureVariableNotFoundException.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.exception;
+package eu.passage.upperware.commons.exception;
import lombok.Getter;
import lombok.Setter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/ValidationException.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/exception/ValidationException.java
similarity index 85%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/ValidationException.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/exception/ValidationException.java
index 2bd3c097156583614d27e870906255e87d224f22..b5ca3734a950d4538630483dad2ad21f28237c0e 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/ValidationException.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/exception/ValidationException.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.exception;
+package eu.passage.upperware.commons.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/properties/GuiYamlData.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/GuiYamlData.java
similarity index 63%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/properties/GuiYamlData.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/GuiYamlData.java
index 59f9aedcec5d0e65bf0031ca50726fa4a7e0f436..04deefbcfc992feabae3d9fb59113585fbf4314c 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/properties/GuiYamlData.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/GuiYamlData.java
@@ -1,7 +1,7 @@
-package eu.melodic.upperware.guibackend.properties;
+package eu.passage.upperware.commons.model;
-import eu.melodic.upperware.guibackend.model.byon.ByonDefinition;
-import eu.melodic.upperware.guibackend.model.provider.CloudDefinition;
+import eu.passage.upperware.commons.model.byon.ByonDefinition;
+import eu.passage.upperware.commons.model.provider.CloudDefinition;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/common/SecureVariable.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/SecureVariable.java
similarity index 78%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/common/SecureVariable.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/SecureVariable.java
index 633d187641fd2fb51325e6c61f647233ea9d3a3e..7cfdbfe05b0f1618536a676d7e7f96fa42c7133c 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/common/SecureVariable.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/SecureVariable.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.controller.deployment.common;
+package eu.passage.upperware.commons.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/ByonDefinition.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/ByonDefinition.java
similarity index 88%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/ByonDefinition.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/ByonDefinition.java
index 63b32d2a2b5657409d2b0659dc08063169daf564..853785406fb734ec7b17cdde5a94363b107054a6 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/ByonDefinition.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/ByonDefinition.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.byon;
+package eu.passage.upperware.commons.model.byon;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/ByonEnums.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/ByonEnums.java
similarity index 84%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/ByonEnums.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/ByonEnums.java
index ab06575d9ee47f116f4c9a84d1cc850b969a493f..be2212f75231ed375c422d71c84f4d0ad38cc25e 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/ByonEnums.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/ByonEnums.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.byon;
+package eu.passage.upperware.commons.model.byon;
import lombok.*;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/GeoLocation.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/GeoLocation.java
similarity index 83%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/GeoLocation.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/GeoLocation.java
index 7cc577584876e9ed2f12108cefcae8d59de42c60..dc56684810f6df94d84916d935012a3e50d80418 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/GeoLocation.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/GeoLocation.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.byon;
+package eu.passage.upperware.commons.model.byon;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/IpAddress.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/IpAddress.java
similarity index 83%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/IpAddress.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/IpAddress.java
index 5eb56663c9d1f81f8f52107b6d7d9171f24311b7..58cf61ceffcb02443b84098d474a269826a2cc8a 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/IpAddress.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/IpAddress.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.byon;
+package eu.passage.upperware.commons.model.byon;
import lombok.AllArgsConstructor;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/LoginCredential.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/LoginCredential.java
similarity index 84%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/LoginCredential.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/LoginCredential.java
index 2da0f48a13cbc393c14758581beb2c8157f5d450..5fff7b327ae39f28cfbd6a93c4ade505dcfc4fb2 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/LoginCredential.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/LoginCredential.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.byon;
+package eu.passage.upperware.commons.model.byon;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/NodeProperties.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/NodeProperties.java
similarity index 88%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/NodeProperties.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/NodeProperties.java
index a8d837200740dacc56cdf966670fd5e0af466fe2..6cb0dd5b0c4df3627434cea1ce5c13f13608c678 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/NodeProperties.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/NodeProperties.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.byon;
+package eu.passage.upperware.commons.model.byon;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/OperatingSystem.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/OperatingSystem.java
similarity index 84%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/OperatingSystem.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/OperatingSystem.java
index c66d75d821c2e700abd98d96798b0db7cb139708..f14c32d56b10d4aef13bf7912b2ec777b78f519e 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/byon/OperatingSystem.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/byon/OperatingSystem.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.byon;
+package eu.passage.upperware.commons.model.byon;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Api.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Api.java
similarity index 81%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Api.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Api.java
index 40699ee02d482f264a08c994594704c23643f0af..2e7e2d558f71f4dbe20641ee177f01a2f57517f5 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Api.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Api.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.provider;
+package eu.passage.upperware.commons.model.provider;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudConfiguration.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudConfiguration.java
similarity index 81%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudConfiguration.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudConfiguration.java
index e8086c4c2a4945317c50d9e7db4977504550af6d..5af19758c436dcb544d611a0c96f19ef5ae3ea83 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudConfiguration.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudConfiguration.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.provider;
+package eu.passage.upperware.commons.model.provider;
import lombok.*;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudDefinition.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudDefinition.java
similarity index 84%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudDefinition.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudDefinition.java
index f85d88966bb3f346b94de3b35ca188035293270d..3df0545bffd89976f07ea403bf405e3924c2a963 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudDefinition.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudDefinition.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.provider;
+package eu.passage.upperware.commons.model.provider;
import lombok.*;
diff --git a/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudType.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudType.java
new file mode 100644
index 0000000000000000000000000000000000000000..228692b046072379c8ecbf1b435f7b937f886265
--- /dev/null
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudType.java
@@ -0,0 +1,5 @@
+package eu.passage.upperware.commons.model.provider;
+
+public enum CloudType {
+ PRIVATE, PUBLIC
+}
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Credential.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Credential.java
similarity index 82%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Credential.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Credential.java
index 2fc71011dcf1a8e2fdbfb8db439f63efcaa7d8b3..53fe5d876cc6ccc527b35ab32769e059cc994c50 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Credential.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Credential.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.provider;
+package eu.passage.upperware.commons.model.provider;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/ParentProperty.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/ParentProperty.java
similarity index 80%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/ParentProperty.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/ParentProperty.java
index 10b63a53a5487f1aed01759216ddb248e01e9257..6004624659218da299a73b116240ab3178d66778 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/ParentProperty.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/ParentProperty.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.provider;
+package eu.passage.upperware.commons.model.provider;
import lombok.*;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Provider.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Provider.java
similarity index 82%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Provider.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Provider.java
index ef6d996ae90f9aec5eb12f781df800c0f4178146..0577de4c1062c21bc0b0ce001e5597d96d2064ce 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/Provider.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/Provider.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.provider;
+package eu.passage.upperware.commons.model.provider;
public enum Provider {
AWS_EC2("aws-ec2"),
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/ProviderEnums.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/ProviderEnums.java
similarity index 79%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/ProviderEnums.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/ProviderEnums.java
index eeb473155f4801be3245e1413a4b0e967b312fff..91f3f880445e3ad7cab501b4af37ec2cf2e0f449 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/ProviderEnums.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/ProviderEnums.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.provider;
+package eu.passage.upperware.commons.model.provider;
import lombok.*;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/SingleProperty.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/SingleProperty.java
similarity index 83%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/SingleProperty.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/SingleProperty.java
index fd8626bd47901c92870ff45631e6c881379d53f2..ca0f038a3a8b7a47934d1a47175b3edfe32ac78b 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/SingleProperty.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/SingleProperty.java
@@ -1,4 +1,4 @@
-package eu.melodic.upperware.guibackend.model.provider;
+package eu.passage.upperware.commons.model.provider;
import lombok.AllArgsConstructor;
import lombok.Getter;
diff --git a/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/FunctionTestConfiguration.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/FunctionTestConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ffa58719a9bb59e7de3a44be859dbb1c0252a49
--- /dev/null
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/FunctionTestConfiguration.java
@@ -0,0 +1,12 @@
+package eu.passage.upperware.commons.model.testing;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FunctionTestConfiguration {
+ private String functionName;
+ private String triggerPath;
+ private List testCases;
+}
diff --git a/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestCase.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..29b15255571692595ea68478a448935ba04afad3
--- /dev/null
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestCase.java
@@ -0,0 +1,11 @@
+package eu.passage.upperware.commons.model.testing;
+
+import lombok.Data;
+
+@Data
+public class TestCase {
+ private String functionName;
+ private String event;
+ private String expectedOutput;
+ private String region;
+}
diff --git a/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestConfiguration.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..4779c12a29c592eea6b3573492a345301bdce6c3
--- /dev/null
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestConfiguration.java
@@ -0,0 +1,10 @@
+package eu.passage.upperware.commons.model.testing;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class TestConfiguration {
+ private List tests;
+}
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderIdCreatorService.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderIdCreatorService.java
similarity index 95%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderIdCreatorService.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderIdCreatorService.java
index 87d594171dba967c423b4626c59909525b5e43ac..a601ecefdb9f6d31f00190c22beea504ed1614b0 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderIdCreatorService.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderIdCreatorService.java
@@ -1,8 +1,8 @@
-package eu.melodic.upperware.guibackend.service.provider;
+package eu.passage.upperware.commons.service.provider;
-import eu.melodic.upperware.guibackend.model.provider.CloudDefinition;
-import eu.melodic.upperware.guibackend.model.provider.ParentProperty;
-import eu.melodic.upperware.guibackend.model.provider.SingleProperty;
+import eu.passage.upperware.commons.model.provider.CloudDefinition;
+import eu.passage.upperware.commons.model.provider.ParentProperty;
+import eu.passage.upperware.commons.model.provider.SingleProperty;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderService.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderService.java
similarity index 93%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderService.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderService.java
index 6c7eadbfe573529e4d4f50537d1a3d9401ab6bf6..aff89ae43294c7ec8c1a632fdb1efe82a2d8a9ca 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderService.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderService.java
@@ -1,12 +1,12 @@
-package eu.melodic.upperware.guibackend.service.provider;
-
-import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi;
-import eu.melodic.upperware.guibackend.exception.CloudDefinitionNotFoundException;
-import eu.melodic.upperware.guibackend.model.provider.CloudDefinition;
-import eu.melodic.upperware.guibackend.model.provider.Provider;
-import eu.melodic.upperware.guibackend.model.provider.ProviderEnums;
-import eu.melodic.upperware.guibackend.service.secure.store.SecureStoreService;
-import eu.melodic.upperware.guibackend.service.yaml.YamlDataService;
+package eu.passage.upperware.commons.service.provider;
+
+import eu.passage.upperware.commons.cloudiator.CloudiatorApi;
+import eu.passage.upperware.commons.exception.CloudDefinitionNotFoundException;
+import eu.passage.upperware.commons.model.provider.CloudDefinition;
+import eu.passage.upperware.commons.model.provider.Provider;
+import eu.passage.upperware.commons.model.provider.ProviderEnums;
+import eu.passage.upperware.commons.service.store.SecureStoreService;
+import eu.passage.upperware.commons.service.yaml.YamlDataService;
import io.github.cloudiator.rest.model.CloudType;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderValidationService.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderValidationService.java
similarity index 95%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderValidationService.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderValidationService.java
index 5c2f9204a9eae6e46f9cc3f9e21d876c884eb562..e188a6c4c1a3c80b80402398999d18459eadb588 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/provider/ProviderValidationService.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/provider/ProviderValidationService.java
@@ -1,7 +1,7 @@
-package eu.melodic.upperware.guibackend.service.provider;
+package eu.passage.upperware.commons.service.provider;
-import eu.melodic.upperware.guibackend.exception.ValidationException;
-import eu.melodic.upperware.guibackend.model.provider.CloudDefinition;
+import eu.passage.upperware.commons.exception.ValidationException;
+import eu.passage.upperware.commons.model.provider.CloudDefinition;
import org.springframework.stereotype.Service;
import java.util.List;
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/secure/store/SecureStoreService.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/store/SecureStoreService.java
similarity index 91%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/secure/store/SecureStoreService.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/service/store/SecureStoreService.java
index f37c34f6bba7460e23be6b7029c9b5a960b8b79f..6f3455cf7b1ceb76b96df1a12111510d70992db0 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/secure/store/SecureStoreService.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/store/SecureStoreService.java
@@ -1,11 +1,11 @@
-package eu.melodic.upperware.guibackend.service.secure.store;
-
-import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi;
-import eu.melodic.upperware.guibackend.controller.deployment.common.SecureVariable;
-import eu.melodic.upperware.guibackend.exception.SecureVariableNotFoundException;
-import eu.melodic.upperware.guibackend.exception.ValidationException;
-import eu.melodic.upperware.guibackend.model.byon.LoginCredential;
-import eu.melodic.upperware.guibackend.model.provider.CloudDefinition;
+package eu.passage.upperware.commons.service.store;
+
+import eu.passage.upperware.commons.cloudiator.CloudiatorApi;
+import eu.passage.upperware.commons.model.SecureVariable;
+import eu.passage.upperware.commons.exception.SecureVariableNotFoundException;
+import eu.passage.upperware.commons.exception.ValidationException;
+import eu.passage.upperware.commons.model.byon.LoginCredential;
+import eu.passage.upperware.commons.model.provider.CloudDefinition;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
diff --git a/melodic-commons/src/main/java/eu/passage/upperware/commons/service/testing/TestConfigurationValidationService.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/testing/TestConfigurationValidationService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e0b8bc0377c524b80ea4f37152fa11abfe46c786
--- /dev/null
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/testing/TestConfigurationValidationService.java
@@ -0,0 +1,113 @@
+package eu.passage.upperware.commons.service.testing;
+
+import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration;
+import eu.passage.upperware.commons.model.testing.TestCase;
+import eu.passage.upperware.commons.model.testing.TestConfiguration;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class TestConfigurationValidationService {
+
+ public static class NotUniqueFunctionNameException extends Exception {
+ String functionName;
+ String message;
+
+ NotUniqueFunctionNameException(String functionName) {
+ this.functionName = functionName;
+ this.message = String.format(
+ "Function name '%s' occurred in more than one test. Please adjust the config file",
+ functionName
+ );
+ }
+
+ @Override
+ public String getMessage() {
+ return this.message;
+ }
+ }
+
+ public static class NotUniqueTestCaseException extends Exception {
+ String functionName;
+ String event;
+ String expectedOutput;
+ String message;
+
+ NotUniqueTestCaseException(String functionName, String event, String expectedOutput) {
+ this.functionName = functionName;
+ this.event = event;
+ this.expectedOutput = expectedOutput;
+ this.message = String.format(
+ "The pair of event = '%s' and expected output = '%s'" +
+ " appears in more than one test case of function '%s'." +
+ " Please adjust the test cases to be unique",
+ this.event,
+ this.expectedOutput,
+ this.functionName
+ );
+ }
+ @Override
+ public String getMessage() {
+ return this.message;
+ }
+ }
+
+ public static void validate(TestConfiguration configuration) throws Exception {
+ log.info("Checking uniqueness of function names");
+ checkFunctionNamesUniqueness(configuration.getTests());
+
+ log.info("Checking uniqueness of test cases");
+ for (FunctionTestConfiguration functionTestConfiguration : configuration.getTests()){
+ checkTestCasesUniqueness(
+ functionTestConfiguration.getTestCases(),
+ functionTestConfiguration.getFunctionName()
+ );
+ }
+ }
+
+ public static void checkFunctionNamesUniqueness(
+ List functionTestConfigurations
+ ) throws NotUniqueFunctionNameException {
+ List functionNames = functionTestConfigurations
+ .stream()
+ .map(FunctionTestConfiguration::getFunctionName)
+ .collect(Collectors.toList());
+ Set uniqueNames = new HashSet<>();
+ for (String name: functionNames) {
+ if (!uniqueNames.add(name)) {
+ log.error("Function name '{}' occurred in more than one test", name);
+ throw new NotUniqueFunctionNameException(name);
+ }
+ }
+ }
+
+ public static void checkTestCasesUniqueness(
+ List testCases, String functionName
+ ) throws NotUniqueTestCaseException {
+ List> eventsExpectedOutputs = testCases
+ .stream()
+ .map(testCase -> Pair.of(testCase.getEvent(), testCase.getExpectedOutput()))
+ .collect(Collectors.toList());
+ Set> uniquePairs = new HashSet<>();
+ for (Pair eventExpectedOutput : eventsExpectedOutputs) {
+ if (!uniquePairs.add(eventExpectedOutput)) {
+ log.error(
+ "Function '{}' has more than one test case with event '{}' and expected output '{}'",
+ functionName,
+ eventExpectedOutput.getKey(),
+ eventExpectedOutput.getValue()
+ );
+ throw new NotUniqueTestCaseException(
+ functionName,
+ eventExpectedOutput.getKey(),
+ eventExpectedOutput.getValue()
+ );
+ }
+ }
+ }
+}
diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/yaml/YamlDataService.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/yaml/YamlDataService.java
similarity index 90%
rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/yaml/YamlDataService.java
rename to melodic-commons/src/main/java/eu/passage/upperware/commons/service/yaml/YamlDataService.java
index 643d15b617c711973e8e6ae5dd4e494a90afee72..3db6ea7831c2ac18f04d1208944b50b6129c9c66 100644
--- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/yaml/YamlDataService.java
+++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/service/yaml/YamlDataService.java
@@ -1,8 +1,8 @@
-package eu.melodic.upperware.guibackend.service.yaml;
+package eu.passage.upperware.commons.service.yaml;
-import eu.melodic.upperware.guibackend.model.byon.ByonDefinition;
-import eu.melodic.upperware.guibackend.model.provider.CloudDefinition;
-import eu.melodic.upperware.guibackend.properties.GuiYamlData;
+import eu.passage.upperware.commons.model.byon.ByonDefinition;
+import eu.passage.upperware.commons.model.provider.CloudDefinition;
+import eu.passage.upperware.commons.model.GuiYamlData;
import lombok.extern.slf4j.Slf4j;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
diff --git a/gui-backend/src/main/resources/gui-data.yaml b/melodic-commons/src/main/resources/gui-data.yaml
similarity index 95%
rename from gui-backend/src/main/resources/gui-data.yaml
rename to melodic-commons/src/main/resources/gui-data.yaml
index a7f6a8007fded3940ac329b29521675516f69f38..17abda4c1fa034693ff79a37c2dc84dc062b6dd6 100644
--- a/gui-backend/src/main/resources/gui-data.yaml
+++ b/melodic-commons/src/main/resources/gui-data.yaml
@@ -1,4 +1,4 @@
-!!eu.melodic.upperware.guibackend.properties.GuiYamlData
+!!eu.passage.upperware.commons.model.GuiYamlData
byonDefinitions:
cloudDefinitions:
- api: {id: 1, providerName: openstack4j}
diff --git a/pom.xml b/pom.xml
index 3f8008846616eaf8eaf962a0344ee3568411f29b..9deff3ca3e4c8b5066e760b297cca254cca8b20b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,7 @@
mq-http-adapter
penalty-calculator
zpp-solver
+ functionizer-testing-tool