From 8a41aef61cbc09b5b59ab064997a8782df21f5ab Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Mon, 20 Apr 2020 15:31:51 +0200 Subject: [PATCH 01/18] functionizer testing tool initial commit --- functionizer-testing-tool/.gitignore | 31 ++ .../.mvn/wrapper/MavenWrapperDownloader.java | 117 +++++++ .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + functionizer-testing-tool/mvnw | 310 ++++++++++++++++++ functionizer-testing-tool/mvnw.cmd | 182 ++++++++++ functionizer-testing-tool/pom.xml | 150 +++++++++ .../src/main/docker/Dockerfile | 9 + .../src/main/docker/run.sh | 3 + .../ApplicationContext.java | 75 +++++ .../FunctionizerTestingToolApplication.java | 17 + .../cloudiator/CloudiatorApi.java | 52 +++ .../cloudiator/CloudiatorClientApi.java | 282 ++++++++++++++++ .../cloudiator/QueueInspector.java | 53 +++ .../FunctionizerTestingToolController.java | 57 ++++ .../SecureVariableNotFoundException.java | 17 + .../model/Provider/Provider.java | 17 + .../model/invoke/InvokeFunctionRequest.java | 12 + .../model/test/FunctionTestConfiguration.java | 11 + .../model/test/TestCase.java | 9 + .../model/test/TestConfiguration.java | 11 + .../security/WebSecurity.java | 48 +++ .../service/TestingService.java | 177 ++++++++++ .../service/provider/AWSLambdaService.java | 76 +++++ .../provider/AzureFunctionsService.java | 35 ++ .../provider/GoogleCloudFunctionsService.java | 16 + .../service/provider/ProviderService.java | 11 + .../yaml/TestConfigurationService.java | 58 ++++ .../test/AnotherTest.java | 38 +++ .../test/FunctionizerFunctionsTest.java | 51 +++ ...nctionizerTestingToolApplicationTests.java | 13 + pom.xml | 3 +- 32 files changed, 1942 insertions(+), 1 deletion(-) create mode 100644 functionizer-testing-tool/.gitignore create mode 100644 functionizer-testing-tool/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 functionizer-testing-tool/.mvn/wrapper/maven-wrapper.jar create mode 100644 functionizer-testing-tool/.mvn/wrapper/maven-wrapper.properties create mode 100755 functionizer-testing-tool/mvnw create mode 100644 functionizer-testing-tool/mvnw.cmd create mode 100644 functionizer-testing-tool/pom.xml create mode 100644 functionizer-testing-tool/src/main/docker/Dockerfile create mode 100644 functionizer-testing-tool/src/main/docker/run.sh create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/ApplicationContext.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplication.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorApi.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorClientApi.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/QueueInspector.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/exception/SecureVariableNotFoundException.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/invoke/InvokeFunctionRequest.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestConfiguration.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestingService.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AWSLambdaService.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AzureFunctionsService.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/GoogleCloudFunctionsService.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/ProviderService.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationService.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/AnotherTest.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/FunctionizerFunctionsTest.java create mode 100644 functionizer-testing-tool/src/test/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplicationTests.java diff --git a/functionizer-testing-tool/.gitignore b/functionizer-testing-tool/.gitignore new file mode 100644 index 000000000..a2a3040aa --- /dev/null +++ b/functionizer-testing-tool/.gitignore @@ -0,0 +1,31 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/functionizer-testing-tool/.mvn/wrapper/MavenWrapperDownloader.java b/functionizer-testing-tool/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 000000000..e76d1f324 --- /dev/null +++ b/functionizer-testing-tool/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.jar b/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.properties b/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..642d572ce --- /dev/null +++ b/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/functionizer-testing-tool/mvnw b/functionizer-testing-tool/mvnw new file mode 100755 index 000000000..a16b5431b --- /dev/null +++ b/functionizer-testing-tool/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/functionizer-testing-tool/mvnw.cmd b/functionizer-testing-tool/mvnw.cmd new file mode 100644 index 000000000..c8d43372c --- /dev/null +++ b/functionizer-testing-tool/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/functionizer-testing-tool/pom.xml b/functionizer-testing-tool/pom.xml new file mode 100644 index 000000000..e9391075b --- /dev/null +++ b/functionizer-testing-tool/pom.xml @@ -0,0 +1,150 @@ + + + + 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.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + org.projectlombok + lombok + + + + com.amazonaws + aws-java-sdk + 1.11.327 + + + + com.microsoft.azure.functions + azure-functions-java-library + 1.3.1 + + + + com.google.apis + google-api-services-cloudfunctions + v1-rev20200219-1.30.9 + + + + org.hamcrest + hamcrest + 2.1 + compile + + + org.testng + testng + RELEASE + compile + + + org.springframework + spring-test + 5.2.5.RELEASE + compile + + + org.springframework.boot + spring-boot-test-autoconfigure + 2.2.6.RELEASE + compile + + + org.junit.jupiter + junit-jupiter-api + 5.5.2 + compile + + + org.assertj + assertj-core + 3.13.2 + compile + + + + org.springframework.boot + spring-boot-starter-actuator + + + junit + junit + + + eu.melodic + gui-backend + 3.1.0-SNAPSHOT + compile + + + + + + + + + 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 000000000..50b4981b4 --- /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 000000000..c03593e7e --- /dev/null +++ b/functionizer-testing-tool/src/main/docker/run.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/config/wait-for-cdo.sh && java -Djavax.net.ssl.trustStore=/config/common/melodic-truststore.p12 -Djavax.net.ssl.trustStorePassword=melodic -Djavax.net.ssl.trustStoreType=pkcs12 -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/ApplicationContext.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/ApplicationContext.java new file mode 100644 index 000000000..2a595874b --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/ApplicationContext.java @@ -0,0 +1,75 @@ +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 eu.passage.upperware.commons.cloudiator.CloudiatorProperties; +import io.github.cloudiator.rest.ApiClient; +import io.github.cloudiator.rest.api.*; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class ApplicationContext { + + @Bean + public RestTemplate getRestTemplate() { + + SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); + simpleClientHttpRequestFactory.setConnectTimeout(180000); + simpleClientHttpRequestFactory.setReadTimeout(120000); + + return new RestTemplate(simpleClientHttpRequestFactory); + } + + @Bean + public ApiClient apiClient(CloudiatorProperties cloudiatorProperties) { + ApiClient apiClient = new ApiClient(); + apiClient.setBasePath(cloudiatorProperties.getCloudiator().getUrl()); + apiClient.setApiKey(cloudiatorProperties.getCloudiator().getApiKey()); + apiClient.setReadTimeout(cloudiatorProperties.getCloudiator().getHttpReadTimeout()); + return apiClient; + } + + @Bean + public CloudApi cloudApi(ApiClient apiClient) { + return new CloudApi(apiClient); + } + + @Bean + public SecurityApi securityApi(ApiClient apiClient) { + return new SecurityApi(apiClient); + } + + @Bean + public NodeApi nodeApi(ApiClient apiClient) { + return new NodeApi(apiClient); + } + + @Bean + public QueueApi queueApi(ApiClient apiClient) { + return new QueueApi(apiClient); + } + + @Bean + public ProcessApi processApi(ApiClient apiClient) { + return new ProcessApi((apiClient)); + } + + @Bean + public JobApi jobApi(ApiClient apiClient) { + return new JobApi(apiClient); + } + + @Bean + public MonitoringApi monitoringApi(ApiClient apiClient) { + return new MonitoringApi(apiClient); + } + + @Bean + public JWTService jWTService(MelodicSecurityProperties melodicSecurityProperties) { + return new JWTServiceImpl(melodicSecurityProperties); + } +} 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 000000000..7a5286673 --- /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/communication/cloudiator/CloudiatorApi.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorApi.java new file mode 100644 index 000000000..1b9b07d77 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorApi.java @@ -0,0 +1,52 @@ +package eu.functionizer.functionizertestingtool.communication.cloudiator; + + +import io.github.cloudiator.rest.model.*; + +import java.util.List; + +public interface CloudiatorApi { + Integer getDiscoveryStatusTotal(); + + List getHardwareList(); + + List getLocationList(); + + List getImageList(); + + List getCloudList(); + + List getFunctionList(); + + String getSecureVariable(String key); + + void deleteSecureVariable(String key); + + List getVMByonFromNodeList(); + + List getFaasFromNodeList(); + + void storeSecureVariable(String key, String value); + + List getNodeList(); + + List getProcessList(); + + List getQueueList(); + + List getJobList(); + + void deleteNode(String nodeId); + + void deleteCloudiatorProcess(String processId); + + List getScheduleList(); + + List getMonitorList(); + + List getByonsList(); + + ByonNode createNewByonNode(NewNode newNode); + + void deleteByon(String byonId); +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorClientApi.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorClientApi.java new file mode 100644 index 000000000..7fcde2f3b --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorClientApi.java @@ -0,0 +1,282 @@ +package eu.functionizer.functionizertestingtool.communication.cloudiator; + + +import eu.functionizer.functionizertestingtool.communication.cloudiator.CloudiatorApi; +import eu.functionizer.functionizertestingtool.communication.cloudiator.QueueInspector; +import eu.functionizer.functionizertestingtool.exception.SecureVariableNotFoundException; +import io.github.cloudiator.rest.ApiException; +import io.github.cloudiator.rest.api.*; +import io.github.cloudiator.rest.model.*; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import javax.ws.rs.NotFoundException; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Service +@AllArgsConstructor(onConstructor = @__({@Autowired})) +public class CloudiatorClientApi implements CloudiatorApi { + + private CloudApi cloudApi; + private SecurityApi securityApi; + private NodeApi nodeApi; + private ProcessApi processApi; + private QueueApi queueApi; + private JobApi jobApi; + private MonitoringApi monitoringApi; + private QueueInspector queueInspector; + private final static String CLOUDIATOR_ERROR_MESSAGE = "Problem in communication with Cloudiator. Cloudiator not working. Please try again."; + + @Override + public Integer getDiscoveryStatusTotal() { + try { + return Integer.valueOf(cloudApi.discoveryStatus().getOrDefault("total", "0")); + } catch (ApiException e) { + log.error("Error by getting total number of offers.", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getHardwareList() { + try { + List hardware = cloudApi.findHardware(null); + log.info("Number of hardware in response = {}", hardware.size()); + return hardware; + } catch (ApiException e) { + log.error("Error by getting hardware list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getLocationList() { + try { + List locations = cloudApi.findLocations(null); + log.info("Number of locations in response: {}", locations.size()); + return locations; + } catch (ApiException e) { + log.error("Error by getting location list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getImageList() { + try { + List images = cloudApi.findImages(null); + log.info("Number of images in response: {}", images.size()); + return images; + } catch (ApiException e) { + log.error("Error by getting images list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getCloudList() { + try { + List clouds = cloudApi.findClouds(); + log.info("Number of clouds in response: {}", clouds.size()); + return clouds; + } catch (ApiException e) { + log.error("Error by getting clouds list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getFunctionList() { + try { + List functions = cloudApi.findFunctions(); + log.info("Number of functions in response: {}", functions.size()); + return functions; + } catch (ApiException e) { + log.error("Error by getting functions list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public void storeSecureVariable(String key, String value) { + Text cloudiatorText = new Text(); + try { + securityApi.storeSecure(key, cloudiatorText.content(value)); + } catch (ApiException ex) { + log.error("Error by secure storing of variable with name: {}", key, ex); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format("Error by storing secure variable with name: %s in Cloudiator's secure store", key)); + } + } + + @Override + public String getSecureVariable(String key) { + try { + log.info("GET secure variable with key: {}", key); + return securityApi.retrieveSecure(key).getContent(); + } catch (ApiException e) { + if (e.getResponseBody() != null && e.getResponseBody().startsWith("Response code 404")) { + log.error("Secure variable not found error"); + throw new SecureVariableNotFoundException(String.format("Secure variable with key %s not found in secure store", key), key); + } + log.error("Error by getting secure variable with name: {}", key, e); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("Error by getting secure variable with name: %s from Cloudiator's secure store", key)); + } + } + + @Override + public void deleteSecureVariable(String key) { + try { + securityApi.deleteSecure(key); + } catch (ApiException e) { + if (e.getResponseBody().startsWith("Response code 404")) { + log.warn("Secure variable with name {} not found in secure store.", key); + throw new NotFoundException(String.format("Secure variable with name %s not found in secure store.", key)); + } else { + log.error("Error by deleting secure variable with name: {}", key, e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format("Error by deleting secure variable with name: %s from Cloudiator's secure store", key)); + } + } + } + + @Override + public List getVMByonFromNodeList() { + return ListUtils.union(this.getNodeWithTypeFromNodeList(Node.NodeTypeEnum.BYON), this.getNodeWithTypeFromNodeList(Node.NodeTypeEnum.VM)); + } + + @Override + public List getFaasFromNodeList() { + return this.getNodeWithTypeFromNodeList(Node.NodeTypeEnum.FAAS); + } + + private List getNodeWithTypeFromNodeList(Node.NodeTypeEnum nodeTypeEnum) { + try { + List vmsFromNode = nodeApi.findNodes() + .stream() + .filter(node -> nodeTypeEnum.equals(node.getNodeType())) + .collect(Collectors.toList()); + log.info("Number of {} nodes in response: {}", nodeTypeEnum, vmsFromNode.size()); + return vmsFromNode; + } catch (ApiException e) { + log.error("Error by getting {} nodes list: ", nodeTypeEnum, e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getNodeList() { + try { + return nodeApi.findNodes(); + } catch (ApiException e) { + log.error("Error by getting nodes list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public void deleteNode(String nodeId) { + try { + nodeApi.deleteNode(nodeId); + } catch (ApiException e) { + log.error("Error by deleting node with id: {}", nodeId, e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getProcessList() { + try { + return processApi.getProcesses(null); + } catch (ApiException e) { + log.error("Error by getting Cloudiator processes list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public void deleteCloudiatorProcess(String processId) { + try { + processApi.deleteProcess(processId); + } catch (ApiException e) { + log.error("Error by deleting process with id: {}", processId, e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getQueueList() { + try { + return queueApi.getQueuedTasks(); + } catch (ApiException e) { + log.error("Error by getting Cloudiator queues list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getJobList() { + try { + return jobApi.findJobs(); + } catch (ApiException e) { + log.error("Error by getting Cloudiator jobs list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getScheduleList() { + try { + return processApi.getSchedules(); + } catch (ApiException e) { + log.error("Error by getting Cloudiator schedules list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getMonitorList() { + try { + return monitoringApi.findMonitors(); + } catch (ApiException e) { + log.error("Error by getting monitors list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public List getByonsList() { + try { + return nodeApi.findByons(); + } catch (ApiException e) { + log.error("Error by getting Cloudiator Byons list: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public ByonNode createNewByonNode(NewNode newNode) { + try { + return nodeApi.addByon(newNode); + } catch (ApiException e) { + log.error("Error by creating new Byon: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } + + @Override + public void deleteByon(String byonId) { + try { + Queue deleteByonQueue = nodeApi.deleteByon(byonId); + queueInspector.waitForQueueFinish(deleteByonQueue.getId()); + } catch (ApiException e) { + log.error("Error by deleting Byon from Cloudiator: ", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); + } + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/QueueInspector.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/QueueInspector.java new file mode 100644 index 000000000..fb9c98d07 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/QueueInspector.java @@ -0,0 +1,53 @@ +package eu.functionizer.functionizertestingtool.communication.cloudiator; + +import eu.passage.upperware.commons.cloudiator.CloudiatorProperties; +import io.github.cloudiator.rest.ApiException; +import io.github.cloudiator.rest.api.QueueApi; +import io.github.cloudiator.rest.model.Queue; +import io.github.cloudiator.rest.model.QueueStatus; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@Slf4j +@Service +@AllArgsConstructor(onConstructor = @__({@Autowired})) +public class QueueInspector { + + private QueueApi queueApi; + private CloudiatorProperties cloudiatorProperties; + + public void waitForQueueFinish(String queueId) { + boolean continueWaiting = true; + Queue queuedTask; + log.info("Waiting for queued task with id: {}", queueId); + do { + log.debug("Queue with id: {} checking", queueId); + try { + queuedTask = queueApi.findQueuedTask(queueId); + if (QueueStatus.COMPLETED.equals(queuedTask.getStatus())) { + log.info("Queued task with id: {} completed", queueId); + continueWaiting = false; + } else if (QueueStatus.FAILED.equals(queuedTask.getStatus())) { + log.error("Queued task with id: {} failed: {}", queueId, queuedTask.getDiagnosis()); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, queuedTask.getDiagnosis()); + } + + } catch (ApiException e) { + String errorMessage = String.format("Error by checking queue with id %s status.", queueId); + log.error(errorMessage, e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); + } + try { + Thread.sleep(cloudiatorProperties.getCloudiator().getDelayBetweenQueueCheck()); + } catch (InterruptedException e) { + log.error("Waiting for finish of queued task with id {} interrupted: ", queueId, e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format("Problem on backend side: waiting for finish of queued task with id %s interrupted.", queueId)); + } + + } while (QueueStatus.SCHEDULED.equals(queuedTask.getStatus()) || QueueStatus.RUNNING.equals(queuedTask.getStatus()) || continueWaiting); + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java new file mode 100644 index 000000000..b169b814f --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java @@ -0,0 +1,57 @@ +package eu.functionizer.functionizertestingtool.controller; + +import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; +import eu.functionizer.functionizertestingtool.service.TestingService; +import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +@RestController +@AllArgsConstructor(onConstructor = @__(@Autowired)) +public class FunctionizerTestingToolController { + + private TestingService testingService; + + @RequestMapping("/") + @ResponseStatus(HttpStatus.OK) + public String index() { + return "Welcome to the Functionizer testing tool!"; + } + + @PostMapping("/test") + @ResponseStatus(HttpStatus.OK) + public String testFunctions() { + return testingService.testFunctions(); + } + + @RequestMapping("/aws-lambda") + @ResponseStatus(HttpStatus.OK) + public List getAWSLambdaFunctions() { + return testingService.listLambdas(); + } + + @PostMapping("/aws-lambda/invoke") + @ResponseStatus(HttpStatus.OK) + public String invokeLambda(@RequestBody InvokeFunctionRequest request) { + + return testingService.invokeLambdaFunction(request); + } + + + @PostMapping("/gcloud-functions/invoke") + @ResponseStatus(HttpStatus.OK) + public String invokeGCloudFunction(@RequestBody InvokeFunctionRequest request) { + + return testingService.invokeGCloudFunction(request); + } + + @PostMapping("/execute-command") + @ResponseStatus(HttpStatus.OK) + public String executeCommand() { + return testingService.executeCommand(new String[]{"/bin/bash", "ls"}); + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/exception/SecureVariableNotFoundException.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/exception/SecureVariableNotFoundException.java new file mode 100644 index 000000000..e462f37cf --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/exception/SecureVariableNotFoundException.java @@ -0,0 +1,17 @@ +package eu.functionizer.functionizertestingtool.exception; + +import lombok.Getter; +import lombok.Setter; + +import javax.ws.rs.NotFoundException; + +@Getter +@Setter +public class SecureVariableNotFoundException extends NotFoundException { + private String missingVariableName; + + public SecureVariableNotFoundException(String message, String missingVariableName) { + super(message); + this.missingVariableName = missingVariableName; + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java new file mode 100644 index 000000000..ca62a8e59 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java @@ -0,0 +1,17 @@ +package eu.functionizer.functionizertestingtool.model.Provider; + +public enum Provider { + AWS_LAMBDA("aws-ec2"), + AZURE("azure-functions"), + GOOGLE("google-cloud-functions"); + + public final String value; + + Provider(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/invoke/InvokeFunctionRequest.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/invoke/InvokeFunctionRequest.java new file mode 100644 index 000000000..07147ea68 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/invoke/InvokeFunctionRequest.java @@ -0,0 +1,12 @@ +package eu.functionizer.functionizertestingtool.model.invoke; + +import lombok.Data; + +@Data +public class InvokeFunctionRequest { + private String functionName; + private String payload; + private String expectedOutput; + private String functionUri; + private String functionKey; +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java new file mode 100644 index 000000000..90bcba6d9 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java @@ -0,0 +1,11 @@ +package eu.functionizer.functionizertestingtool.model.test; + +import lombok.Data; + +import java.util.List; + +@Data +public class FunctionTestConfiguration { + private String functionName; + private List testCases; +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java new file mode 100644 index 000000000..29c4cf8cc --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java @@ -0,0 +1,9 @@ +package eu.functionizer.functionizertestingtool.model.test; + +import lombok.Data; + +@Data +public class TestCase { + private String event; + private String expectedOutput; +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestConfiguration.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestConfiguration.java new file mode 100644 index 000000000..895d1b3f4 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestConfiguration.java @@ -0,0 +1,11 @@ +package eu.functionizer.functionizertestingtool.model.test; + + +import lombok.Data; + +import java.util.List; + +@Data +public class TestConfiguration { + private List tests; +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java new file mode 100644 index 000000000..77747a72d --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java @@ -0,0 +1,48 @@ +package eu.functionizer.functionizertestingtool.security; + + +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; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +@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.csrf().disable().antMatcher("/auth/**") + .authorizeRequests() + .anyRequest().authenticated() + .and() + .addFilterBefore(new JWTAuthorizationFilter(authenticationManager(), jwtService), BasicAuthenticationFilter.class); + http.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/service/TestingService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestingService.java new file mode 100644 index 000000000..a8bdf3b59 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestingService.java @@ -0,0 +1,177 @@ +package eu.functionizer.functionizertestingtool.service; + +import com.amazonaws.services.lambda.model.InvokeRequest; +import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; +import eu.functionizer.functionizertestingtool.model.test.FunctionTestConfiguration; +import eu.functionizer.functionizertestingtool.model.test.TestCase; +import eu.functionizer.functionizertestingtool.model.test.TestConfiguration; + +import eu.functionizer.functionizertestingtool.communication.cloudiator.CloudiatorApi; +import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; +import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; +import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationService; +import io.github.cloudiator.rest.model.*; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; + +import java.io.*; +import java.lang.Runtime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; + +@Service +@Slf4j +@AllArgsConstructor(onConstructor = @__(@Autowired)) +public class TestingService { + + AWSLambdaService awsLambdaService; + AzureFunctionsService azureFunctionsService; + CloudiatorApi cloudiatorApi; + TestConfigurationService testConfigurationService; + + private String extractFunctionName(Function function) { + return Objects.requireNonNull(function.getStackId()).split("-")[0]; + } + + private String extractRegionName(Function function) { + String[] locationSplit = Objects.requireNonNull(function.getLocationId()).split("~"); + return locationSplit[locationSplit.length - 1]; + } + + private Map getFunctionsMap() { + List functions = cloudiatorApi.getFunctionList(); + return functions + .stream() + .collect(Collectors.toMap(this::extractFunctionName, function -> function)); + } + + private Map getCloudsMap() { + List clouds = cloudiatorApi.getCloudList(); + return clouds.stream().collect(Collectors.toMap(Cloud::getId, cloud -> cloud)); + } + + private String getTaskInterfaceFunctionName(Task task) { + FaasInterface taskInterface = (FaasInterface) Objects.requireNonNull(task.getInterfaces()).get(0); + return taskInterface.getFunctionName(); + } + + private Map getFunctionSuffices() { + List jobs = cloudiatorApi.getJobList(); + List tasks = jobs.stream() + .map(Job::getTasks) + .filter(Objects::nonNull) + .flatMap(List::stream) + .filter(task -> FaasInterface.class.equals(Objects.requireNonNull(task.getInterfaces()).get(0).getClass())) + .collect(Collectors.toList()); + return tasks.stream().collect(Collectors.toMap(Task::getName, this::getTaskInterfaceFunctionName)); + } + + @Test + public String testFunctions() { + + List functionTests = testConfigurationService.loadTestConfiguration().getTests(); + Map functions = getFunctionsMap(); + Map clouds = getCloudsMap(); + Map functionSuffices = getFunctionSuffices(); + + for (FunctionTestConfiguration functionTestConfiguration : functionTests) { + String functionNameLower = functionTestConfiguration.getFunctionName().toLowerCase(); + + Function function = functions.get(functionNameLower); + Cloud cloud = clouds.get(function.getCloudId()); + String providerName = cloud.getApi().getProviderName(); + + if (AWSLambdaService.providerName.getValue().equals(providerName)) { + + String region = extractRegionName(function); + String functionName = String.join( + "-", + function.getStackId(), + functionSuffices.get(functionTestConfiguration.getFunctionName()) + ); + testAWSLambdaFunction(functionTestConfiguration, cloud.getCredential(), region, functionName); + + } else if (AzureFunctionsService.providerName.getValue().equals(providerName)) { + // TODO + System.out.println("Testing azure function..."); + } + } + return "tests passed"; + } + + @Test + public void testAWSLambdaFunction( + FunctionTestConfiguration testConfiguration, + CloudCredential credential, + String region, + String functionName + ) { + + awsLambdaService.configureClient(credential, region); + + InvokeRequest invokeRequest = new InvokeRequest().withFunctionName(functionName); + + for (TestCase testCase : testConfiguration.getTestCases()) { + invokeRequest.setPayload(testCase.getEvent()); + + String resultString = awsLambdaService.invokeFunction(invokeRequest); + assertEquals(testCase.getExpectedOutput(), resultString); + } + } + + + public List listLambdas() { + return awsLambdaService.listFunctions(); + } + + public String invokeLambdaFunction(InvokeFunctionRequest request) { + awsLambdaService.listFunctions(); + String ans = awsLambdaService.invokeFunction(request); + System.out.println(ans); + return ans; + } + + public String invokeAzureFunction(InvokeFunctionRequest request) { + return azureFunctionsService.invokeFunction(request); + } + + + public String executeCommand(String[] command) { + StringBuilder output = new StringBuilder(); + try { + Process process = Runtime.getRuntime().exec(command); + process.waitFor(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line = ""; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + } catch (Exception e) { + e.printStackTrace(); + } + return output.toString(); + } + + public String invokeGCloudFunction(InvokeFunctionRequest request) { + String[] command = { + "gcloud", + "functions", + "call", + request.getFunctionName(), + "--data", + request.getPayload() + }; + + return executeCommand(command); + } +} 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 000000000..171b067b8 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AWSLambdaService.java @@ -0,0 +1,76 @@ +package eu.functionizer.functionizertestingtool.service.provider; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.auth.profile.ProfileCredentialsProvider; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.lambda.AWSLambda; +import com.amazonaws.services.lambda.AWSLambdaClientBuilder; +import com.amazonaws.services.lambda.model.FunctionConfiguration; +import com.amazonaws.services.lambda.model.InvokeRequest; +import com.amazonaws.services.lambda.model.InvokeResult; +import com.amazonaws.services.lambda.model.ServiceException; +import eu.functionizer.functionizertestingtool.model.Provider.Provider; +import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; +import io.github.cloudiator.rest.model.CloudCredential; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class AWSLambdaService extends ProviderService { + public static Provider providerName = Provider.AWS_LAMBDA; + + private AWSLambda awsLambda; + + public void configureClient(CloudCredential cloudCredential, String region) { + BasicAWSCredentials credentials = new BasicAWSCredentials( + cloudCredential.getUser(), cloudCredential.getSecret() + ); + + awsLambda = AWSLambdaClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(Regions.fromName(region)) + .build(); + } + + public void configureClient() { + awsLambda = AWSLambdaClientBuilder.standard() + .withCredentials(new ProfileCredentialsProvider()) + .withRegion(Regions.EU_WEST_1) + .build(); + } + + + public List listFunctions() { + + List functions = awsLambda.listFunctions().getFunctions(); + + return functions.stream().map(FunctionConfiguration::getFunctionName).collect(Collectors.toList()); + } + + public String invokeFunction(InvokeFunctionRequest request) { + InvokeRequest invokeRequest = new InvokeRequest() + .withFunctionName(request.getFunctionName()) + .withPayload(request.getPayload()); + + try { + InvokeResult result = awsLambda.invoke(invokeRequest); + return new String(result.getPayload().array(), StandardCharsets.UTF_8); + } catch (ServiceException e) { + return null; + } + } + + public String invokeFunction(InvokeRequest invokeRequest) { + + try { + InvokeResult result = awsLambda.invoke(invokeRequest); + return new String(result.getPayload().array(), StandardCharsets.UTF_8); + } catch (ServiceException e) { + return null; + } + } +} 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 000000000..82319a233 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/AzureFunctionsService.java @@ -0,0 +1,35 @@ +package eu.functionizer.functionizertestingtool.service.provider; + + +import eu.functionizer.functionizertestingtool.model.Provider.Provider; +import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + + +import java.util.Collections; + +@Service +public class AzureFunctionsService extends ProviderService { + public static Provider providerName = Provider.AZURE; + + public String invokeFunction(InvokeFunctionRequest request){ + + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.set("x-functions-key", request.getFunctionKey()); + + + HttpEntity entity = new HttpEntity<>(request.getPayload(), headers); + ResponseEntity response = restTemplate.postForEntity( + request.getFunctionUri(), + entity, + String.class + ); + return response.toString(); + } + +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/GoogleCloudFunctionsService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/GoogleCloudFunctionsService.java new file mode 100644 index 000000000..96c0a2b88 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/GoogleCloudFunctionsService.java @@ -0,0 +1,16 @@ +package eu.functionizer.functionizertestingtool.service.provider; + +import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; +import org.springframework.stereotype.Service; +import com.google.api.services.cloudfunctions.v1.CloudFunctions; + +@Service +public class GoogleCloudFunctionsService { + + private CloudFunctions cloudFunctions; + + public String invokeFunction(InvokeFunctionRequest request) { + + return "Ok"; + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/ProviderService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/ProviderService.java new file mode 100644 index 000000000..044158acc --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/ProviderService.java @@ -0,0 +1,11 @@ +package eu.functionizer.functionizertestingtool.service.provider; + +import eu.functionizer.functionizertestingtool.model.Provider.Provider; +import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; + +public abstract class ProviderService { + + public static Provider providerName; + + abstract public Object invokeFunction(InvokeFunctionRequest request); +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationService.java new file mode 100644 index 000000000..d36ec1d23 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationService.java @@ -0,0 +1,58 @@ +package eu.functionizer.functionizertestingtool.service.yaml; + +import eu.functionizer.functionizertestingtool.model.test.TestConfiguration; +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 java.io.*; + +@Service +@Slf4j +public class TestConfigurationService { + + private final static String CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") + "/tests.yml"; + + public TestConfiguration loadTestConfiguration() { + Yaml yaml = new Yaml(new Constructor(TestConfiguration.class)); + + try (FileInputStream fileInputStream = new FileInputStream(new File(CONFIG_FILE_PATH))) { + return yaml.load(fileInputStream); + + } catch (FileNotFoundException e) { + String errorMessage = String.format("File test configuration: %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); + } + } + +// public void updateCloudDefinitionInYamlFile(List cloudDefinitionsForAllProviders) { +// TestConfiguration testConfiguration = loadTestConfiguration(); +// testConfiguration.setCloudDefinitions(cloudDefinitionsForAllProviders); +// saveTestConfiguration(testConfiguration); +// } +// +// public void updateByonDefinitionInYamlFile(List byonDefinitionsList) { +// TestConfiguration testConfiguration = loadTestConfiguration(); +// testConfiguration.setByonDefinitions(byonDefinitionsList); +// saveTestConfiguration(testConfiguration); +// } + + 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/java/eu/functionizer/functionizertestingtool/test/AnotherTest.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/AnotherTest.java new file mode 100644 index 000000000..af4c79292 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/AnotherTest.java @@ -0,0 +1,38 @@ +package eu.functionizer.functionizertestingtool.test; + +import static org.assertj.core.api.Assertions.*; + +import java.net.URL; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.ResponseEntity; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class AnotherTest { + + @LocalServerPort + private int port; + + private URL base; + + @Autowired + private TestRestTemplate template; + + @BeforeEach + public void setUp() throws Exception { + this.base = new URL("http://localhost:" + port + "/"); + } + + @Test + public void getHello() throws Exception { + ResponseEntity response = template.getForEntity(base.toString(), String.class); + assertThat(response.getBody()).isEqualTo("Hello"); + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/FunctionizerFunctionsTest.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/FunctionizerFunctionsTest.java new file mode 100644 index 000000000..1d204c603 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/FunctionizerFunctionsTest.java @@ -0,0 +1,51 @@ +package eu.functionizer.functionizertestingtool.test; + + +import eu.functionizer.functionizertestingtool.model.test.FunctionTestConfiguration; +import eu.functionizer.functionizertestingtool.model.test.TestConfiguration; +import org.junit.jupiter.api.Test; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.springframework.boot.test.context.SpringBootTest; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; + +import java.io.InputStream; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +@SpringBootTest +@RunWith(Parameterized.class) +public class FunctionizerFunctionsTest { + + public static TestConfiguration loadTestConfiguration() { + Yaml yaml = new Yaml(new Constructor(TestConfiguration.class)); + InputStream configFile = FunctionizerFunctionsTest.class + .getClassLoader() + .getResourceAsStream("tests.yml"); + return yaml.load(configFile); + } + + @Parameters + public static List data() { + return loadTestConfiguration().getTests(); + } + + private String functionName; + private String event; + private String expectedOutput; + + public FunctionizerFunctionsTest(String functionName, String event, String expectedOutput) { + this.functionName = functionName; + this.event = event; + this.expectedOutput = expectedOutput; + } + + @Test + public void TestFunctionOutput() { + } + +} diff --git a/functionizer-testing-tool/src/test/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplicationTests.java b/functionizer-testing-tool/src/test/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplicationTests.java new file mode 100644 index 000000000..9d6785e97 --- /dev/null +++ b/functionizer-testing-tool/src/test/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplicationTests.java @@ -0,0 +1,13 @@ +package eu.functionizer.functionizertestingtool; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class FunctionizerTestingToolApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/pom.xml b/pom.xml index e780e77cf..2a8796559 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,7 @@ mq-http-adapter penalty-calculator zpp-solver + functionizer-testing-tool @@ -392,4 +393,4 @@ - \ No newline at end of file + -- GitLab From 75c85a06c502c5f1ab1fc011996b321f7ad0db33 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Mon, 11 May 2020 16:18:14 +0200 Subject: [PATCH 02/18] functionizer testing tool aws and azure test factory --- functionizer-testing-tool/pom.xml | 159 +++++++--- ...onContext.java => TestingToolContext.java} | 31 +- .../cloudiator/CloudiatorApi.java | 52 ---- .../cloudiator/CloudiatorClientApi.java | 282 ------------------ .../cloudiator/QueueInspector.java | 53 ---- .../FunctionizerTestingToolController.java | 39 +-- .../model/Provider/Provider.java | 4 +- .../model/test/FunctionTestConfiguration.java | 1 + .../model/test/TestCase.java | 2 + .../model/test/TestResultResponse.java | 55 ++++ .../service/TestRunner.java | 60 ++++ .../service/TestingService.java | 177 ----------- .../service/provider/AWSLambdaService.java | 54 +--- .../provider/AzureFunctionsService.java | 72 ++++- .../provider/GoogleCloudFunctionsService.java | 16 - .../service/provider/ProviderService.java | 11 - .../test/ServerlessFunctionTestFactory.java | 258 ++++++++++++++++ ...vice.java => TestConfigurationLoader.java} | 18 +- .../test/AnotherTest.java | 38 --- .../test/FunctionizerFunctionsTest.java | 51 ---- ...nctionizerTestingToolApplicationTests.java | 13 - 21 files changed, 606 insertions(+), 840 deletions(-) rename functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/{ApplicationContext.java => TestingToolContext.java} (68%) delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorApi.java delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorClientApi.java delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/QueueInspector.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestResultResponse.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestRunner.java delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestingService.java delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/GoogleCloudFunctionsService.java delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/ProviderService.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/ServerlessFunctionTestFactory.java rename functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/{TestConfigurationService.java => TestConfigurationLoader.java} (71%) delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/AnotherTest.java delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/FunctionizerFunctionsTest.java delete mode 100644 functionizer-testing-tool/src/test/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplicationTests.java diff --git a/functionizer-testing-tool/pom.xml b/functionizer-testing-tool/pom.xml index e9391075b..e8ba2d706 100644 --- a/functionizer-testing-tool/pom.xml +++ b/functionizer-testing-tool/pom.xml @@ -24,38 +24,86 @@ - org.springframework.boot - spring-boot-starter-web + 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-starter + spring-boot + + + org.springframework.boot + spring-boot-autoconfigure - org.springframework.boot spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - org.projectlombok - lombok + org.slf4j + slf4j-api + 1.7.25 + + + org.yaml + snakeyaml + 1.23 + + + + eu.melodic + gui-backend + 3.1.0-SNAPSHOT + compile com.amazonaws - aws-java-sdk + 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 @@ -69,59 +117,43 @@ - org.hamcrest - hamcrest - 2.1 + org.assertj + assertj-core + 3.13.2 compile + - org.testng - testng - RELEASE - compile + junit + junit + - org.springframework - spring-test - 5.2.5.RELEASE - compile + org.junit.platform + junit-platform-launcher - org.springframework.boot - spring-boot-test-autoconfigure - 2.2.6.RELEASE - compile + org.junit.jupiter + junit-jupiter-engine + + + + org.junit.jupiter junit-jupiter-api 5.5.2 compile - - org.assertj - assertj-core - 3.13.2 - compile - - - - org.springframework.boot - spring-boot-starter-actuator - - - junit - junit - - eu.melodic - gui-backend - 3.1.0-SNAPSHOT + org.jetbrains + annotations + RELEASE compile - - + @@ -144,6 +176,35 @@ gmaven-plugin + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.2 + + + copy + package + + copy + + + + + [ groupId ] + [ artifactId ] + [ version ] + [ packaging ] + [classifier - optional] + [ true or false ] + [ output directory ] + [ filename ] + + + + + + + diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/ApplicationContext.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java similarity index 68% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/ApplicationContext.java rename to functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java index 2a595874b..76f066a6b 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/ApplicationContext.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java @@ -1,5 +1,8 @@ package eu.functionizer.functionizertestingtool; +import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; +import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorClientApi; +import eu.melodic.upperware.guibackend.communication.cloudiator.QueueInspector; import eu.paasage.upperware.security.authapi.properties.MelodicSecurityProperties; import eu.paasage.upperware.security.authapi.token.JWTService; import eu.paasage.upperware.security.authapi.token.JWTServiceImpl; @@ -7,12 +10,13 @@ import eu.passage.upperware.commons.cloudiator.CloudiatorProperties; import io.github.cloudiator.rest.ApiClient; import io.github.cloudiator.rest.api.*; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @Configuration -public class ApplicationContext { +public class TestingToolContext { @Bean public RestTemplate getRestTemplate() { @@ -72,4 +76,29 @@ public class ApplicationContext { public JWTService jWTService(MelodicSecurityProperties melodicSecurityProperties) { return new JWTServiceImpl(melodicSecurityProperties); } + + @Bean + public MatchmakingApi matchmakingApi(ApiClient apiClient) { + return new MatchmakingApi(apiClient); + } + + @Bean + QueueInspector queueInspector(QueueApi queueApi, CloudiatorProperties cloudiatorProperties) { + return new QueueInspector(queueApi, cloudiatorProperties); + } + + @Bean + 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 + ); + } } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorApi.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorApi.java deleted file mode 100644 index 1b9b07d77..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorApi.java +++ /dev/null @@ -1,52 +0,0 @@ -package eu.functionizer.functionizertestingtool.communication.cloudiator; - - -import io.github.cloudiator.rest.model.*; - -import java.util.List; - -public interface CloudiatorApi { - Integer getDiscoveryStatusTotal(); - - List getHardwareList(); - - List getLocationList(); - - List getImageList(); - - List getCloudList(); - - List getFunctionList(); - - String getSecureVariable(String key); - - void deleteSecureVariable(String key); - - List getVMByonFromNodeList(); - - List getFaasFromNodeList(); - - void storeSecureVariable(String key, String value); - - List getNodeList(); - - List getProcessList(); - - List getQueueList(); - - List getJobList(); - - void deleteNode(String nodeId); - - void deleteCloudiatorProcess(String processId); - - List getScheduleList(); - - List getMonitorList(); - - List getByonsList(); - - ByonNode createNewByonNode(NewNode newNode); - - void deleteByon(String byonId); -} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorClientApi.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorClientApi.java deleted file mode 100644 index 7fcde2f3b..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/CloudiatorClientApi.java +++ /dev/null @@ -1,282 +0,0 @@ -package eu.functionizer.functionizertestingtool.communication.cloudiator; - - -import eu.functionizer.functionizertestingtool.communication.cloudiator.CloudiatorApi; -import eu.functionizer.functionizertestingtool.communication.cloudiator.QueueInspector; -import eu.functionizer.functionizertestingtool.exception.SecureVariableNotFoundException; -import io.github.cloudiator.rest.ApiException; -import io.github.cloudiator.rest.api.*; -import io.github.cloudiator.rest.model.*; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.ListUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -import javax.ws.rs.NotFoundException; -import java.util.List; -import java.util.stream.Collectors; - -@Slf4j -@Service -@AllArgsConstructor(onConstructor = @__({@Autowired})) -public class CloudiatorClientApi implements CloudiatorApi { - - private CloudApi cloudApi; - private SecurityApi securityApi; - private NodeApi nodeApi; - private ProcessApi processApi; - private QueueApi queueApi; - private JobApi jobApi; - private MonitoringApi monitoringApi; - private QueueInspector queueInspector; - private final static String CLOUDIATOR_ERROR_MESSAGE = "Problem in communication with Cloudiator. Cloudiator not working. Please try again."; - - @Override - public Integer getDiscoveryStatusTotal() { - try { - return Integer.valueOf(cloudApi.discoveryStatus().getOrDefault("total", "0")); - } catch (ApiException e) { - log.error("Error by getting total number of offers.", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getHardwareList() { - try { - List hardware = cloudApi.findHardware(null); - log.info("Number of hardware in response = {}", hardware.size()); - return hardware; - } catch (ApiException e) { - log.error("Error by getting hardware list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getLocationList() { - try { - List locations = cloudApi.findLocations(null); - log.info("Number of locations in response: {}", locations.size()); - return locations; - } catch (ApiException e) { - log.error("Error by getting location list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getImageList() { - try { - List images = cloudApi.findImages(null); - log.info("Number of images in response: {}", images.size()); - return images; - } catch (ApiException e) { - log.error("Error by getting images list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getCloudList() { - try { - List clouds = cloudApi.findClouds(); - log.info("Number of clouds in response: {}", clouds.size()); - return clouds; - } catch (ApiException e) { - log.error("Error by getting clouds list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getFunctionList() { - try { - List functions = cloudApi.findFunctions(); - log.info("Number of functions in response: {}", functions.size()); - return functions; - } catch (ApiException e) { - log.error("Error by getting functions list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public void storeSecureVariable(String key, String value) { - Text cloudiatorText = new Text(); - try { - securityApi.storeSecure(key, cloudiatorText.content(value)); - } catch (ApiException ex) { - log.error("Error by secure storing of variable with name: {}", key, ex); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format("Error by storing secure variable with name: %s in Cloudiator's secure store", key)); - } - } - - @Override - public String getSecureVariable(String key) { - try { - log.info("GET secure variable with key: {}", key); - return securityApi.retrieveSecure(key).getContent(); - } catch (ApiException e) { - if (e.getResponseBody() != null && e.getResponseBody().startsWith("Response code 404")) { - log.error("Secure variable not found error"); - throw new SecureVariableNotFoundException(String.format("Secure variable with key %s not found in secure store", key), key); - } - log.error("Error by getting secure variable with name: {}", key, e); - throw new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("Error by getting secure variable with name: %s from Cloudiator's secure store", key)); - } - } - - @Override - public void deleteSecureVariable(String key) { - try { - securityApi.deleteSecure(key); - } catch (ApiException e) { - if (e.getResponseBody().startsWith("Response code 404")) { - log.warn("Secure variable with name {} not found in secure store.", key); - throw new NotFoundException(String.format("Secure variable with name %s not found in secure store.", key)); - } else { - log.error("Error by deleting secure variable with name: {}", key, e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format("Error by deleting secure variable with name: %s from Cloudiator's secure store", key)); - } - } - } - - @Override - public List getVMByonFromNodeList() { - return ListUtils.union(this.getNodeWithTypeFromNodeList(Node.NodeTypeEnum.BYON), this.getNodeWithTypeFromNodeList(Node.NodeTypeEnum.VM)); - } - - @Override - public List getFaasFromNodeList() { - return this.getNodeWithTypeFromNodeList(Node.NodeTypeEnum.FAAS); - } - - private List getNodeWithTypeFromNodeList(Node.NodeTypeEnum nodeTypeEnum) { - try { - List vmsFromNode = nodeApi.findNodes() - .stream() - .filter(node -> nodeTypeEnum.equals(node.getNodeType())) - .collect(Collectors.toList()); - log.info("Number of {} nodes in response: {}", nodeTypeEnum, vmsFromNode.size()); - return vmsFromNode; - } catch (ApiException e) { - log.error("Error by getting {} nodes list: ", nodeTypeEnum, e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getNodeList() { - try { - return nodeApi.findNodes(); - } catch (ApiException e) { - log.error("Error by getting nodes list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public void deleteNode(String nodeId) { - try { - nodeApi.deleteNode(nodeId); - } catch (ApiException e) { - log.error("Error by deleting node with id: {}", nodeId, e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getProcessList() { - try { - return processApi.getProcesses(null); - } catch (ApiException e) { - log.error("Error by getting Cloudiator processes list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public void deleteCloudiatorProcess(String processId) { - try { - processApi.deleteProcess(processId); - } catch (ApiException e) { - log.error("Error by deleting process with id: {}", processId, e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getQueueList() { - try { - return queueApi.getQueuedTasks(); - } catch (ApiException e) { - log.error("Error by getting Cloudiator queues list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getJobList() { - try { - return jobApi.findJobs(); - } catch (ApiException e) { - log.error("Error by getting Cloudiator jobs list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getScheduleList() { - try { - return processApi.getSchedules(); - } catch (ApiException e) { - log.error("Error by getting Cloudiator schedules list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getMonitorList() { - try { - return monitoringApi.findMonitors(); - } catch (ApiException e) { - log.error("Error by getting monitors list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public List getByonsList() { - try { - return nodeApi.findByons(); - } catch (ApiException e) { - log.error("Error by getting Cloudiator Byons list: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public ByonNode createNewByonNode(NewNode newNode) { - try { - return nodeApi.addByon(newNode); - } catch (ApiException e) { - log.error("Error by creating new Byon: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } - - @Override - public void deleteByon(String byonId) { - try { - Queue deleteByonQueue = nodeApi.deleteByon(byonId); - queueInspector.waitForQueueFinish(deleteByonQueue.getId()); - } catch (ApiException e) { - log.error("Error by deleting Byon from Cloudiator: ", e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, CLOUDIATOR_ERROR_MESSAGE); - } - } -} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/QueueInspector.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/QueueInspector.java deleted file mode 100644 index fb9c98d07..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/communication/cloudiator/QueueInspector.java +++ /dev/null @@ -1,53 +0,0 @@ -package eu.functionizer.functionizertestingtool.communication.cloudiator; - -import eu.passage.upperware.commons.cloudiator.CloudiatorProperties; -import io.github.cloudiator.rest.ApiException; -import io.github.cloudiator.rest.api.QueueApi; -import io.github.cloudiator.rest.model.Queue; -import io.github.cloudiator.rest.model.QueueStatus; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.web.server.ResponseStatusException; - -@Slf4j -@Service -@AllArgsConstructor(onConstructor = @__({@Autowired})) -public class QueueInspector { - - private QueueApi queueApi; - private CloudiatorProperties cloudiatorProperties; - - public void waitForQueueFinish(String queueId) { - boolean continueWaiting = true; - Queue queuedTask; - log.info("Waiting for queued task with id: {}", queueId); - do { - log.debug("Queue with id: {} checking", queueId); - try { - queuedTask = queueApi.findQueuedTask(queueId); - if (QueueStatus.COMPLETED.equals(queuedTask.getStatus())) { - log.info("Queued task with id: {} completed", queueId); - continueWaiting = false; - } else if (QueueStatus.FAILED.equals(queuedTask.getStatus())) { - log.error("Queued task with id: {} failed: {}", queueId, queuedTask.getDiagnosis()); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, queuedTask.getDiagnosis()); - } - - } catch (ApiException e) { - String errorMessage = String.format("Error by checking queue with id %s status.", queueId); - log.error(errorMessage, e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); - } - try { - Thread.sleep(cloudiatorProperties.getCloudiator().getDelayBetweenQueueCheck()); - } catch (InterruptedException e) { - log.error("Waiting for finish of queued task with id {} interrupted: ", queueId, e); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format("Problem on backend side: waiting for finish of queued task with id %s interrupted.", queueId)); - } - - } while (QueueStatus.SCHEDULED.equals(queuedTask.getStatus()) || QueueStatus.RUNNING.equals(queuedTask.getStatus()) || continueWaiting); - } -} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java index b169b814f..2a43840f8 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java @@ -1,20 +1,20 @@ package eu.functionizer.functionizertestingtool.controller; import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; -import eu.functionizer.functionizertestingtool.service.TestingService; +import eu.functionizer.functionizertestingtool.model.test.TestResultResponse; +import eu.functionizer.functionizertestingtool.service.TestRunner; import lombok.AllArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import java.io.IOException; import java.util.List; @RestController @AllArgsConstructor(onConstructor = @__(@Autowired)) public class FunctionizerTestingToolController { - private TestingService testingService; + private TestRunner testRunner; @RequestMapping("/") @ResponseStatus(HttpStatus.OK) @@ -22,36 +22,19 @@ public class FunctionizerTestingToolController { return "Welcome to the Functionizer testing tool!"; } - @PostMapping("/test") - @ResponseStatus(HttpStatus.OK) - public String testFunctions() { - return testingService.testFunctions(); - } - - @RequestMapping("/aws-lambda") - @ResponseStatus(HttpStatus.OK) - public List getAWSLambdaFunctions() { - return testingService.listLambdas(); - } - - @PostMapping("/aws-lambda/invoke") - @ResponseStatus(HttpStatus.OK) - public String invokeLambda(@RequestBody InvokeFunctionRequest request) { - - return testingService.invokeLambdaFunction(request); + @GetMapping(value = "/health") + public void health() { } - - @PostMapping("/gcloud-functions/invoke") + @PostMapping("/test") @ResponseStatus(HttpStatus.OK) - public String invokeGCloudFunction(@RequestBody InvokeFunctionRequest request) { - - return testingService.invokeGCloudFunction(request); + public TestResultResponse runTests() { + return testRunner.runTests(); } - @PostMapping("/execute-command") + @RequestMapping("/report") @ResponseStatus(HttpStatus.OK) - public String executeCommand() { - return testingService.executeCommand(new String[]{"/bin/bash", "ls"}); + public String getTestReport() { + return testRunner.getReport(); } } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java index ca62a8e59..f46f9b57e 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java @@ -1,8 +1,8 @@ package eu.functionizer.functionizertestingtool.model.Provider; public enum Provider { - AWS_LAMBDA("aws-ec2"), - AZURE("azure-functions"), + AWS("aws-ec2"), + AZURE("azure"), GOOGLE("google-cloud-functions"); public final String value; diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java index 90bcba6d9..a19469d31 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java @@ -7,5 +7,6 @@ import java.util.List; @Data public class FunctionTestConfiguration { private String functionName; + private String triggerPath; private List testCases; } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java index 29c4cf8cc..7933a0c2d 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java @@ -4,6 +4,8 @@ import lombok.Data; @Data public class TestCase { + private String functionName; private String event; private String expectedOutput; + private String region; } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestResultResponse.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestResultResponse.java new file mode 100644 index 000000000..06646ef45 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestResultResponse.java @@ -0,0 +1,55 @@ +package eu.functionizer.functionizertestingtool.model.test; + +import lombok.Data; +import org.assertj.core.api.Fail; +import org.junit.Test; +import org.junit.platform.launcher.listeners.TestExecutionSummary; + +import java.util.List; +import java.util.stream.Collectors; + +@Data +public class TestResultResponse { + private long testRunDuration; + + private int totalFound; + private int totalRan; + private int totalSkipped; + + private int totalSucceeded; + private int totalFailed; + + private List failures; + + private static class Failure { + public String displayName; + public String cause; + + public Failure(String displayName, String cause) { + this.displayName = displayName; + this.cause = cause; + } + } + + public static TestResultResponse fromTestSummary(TestExecutionSummary summary) { + TestResultResponse testResultResponse = new TestResultResponse(); + testResultResponse.setTestRunDuration(summary.getTimeFinished() - summary.getTimeStarted()); + testResultResponse.setTotalFound((int) summary.getTestsFoundCount()); + testResultResponse.setTotalRan((int) summary.getTestsStartedCount()); + testResultResponse.setTotalSkipped((int) summary.getTestsSkippedCount()); + testResultResponse.setTotalSucceeded((int) summary.getTestsSucceededCount()); + testResultResponse.setTotalFailed((int) summary.getTestsFailedCount()); + + testResultResponse.setFailures( + summary + .getFailures() + .stream() + .map(failure -> new Failure( + failure.getTestIdentifier().getDisplayName(), + failure.getException().getMessage() + )) + .collect(Collectors.toList()) + ); + return testResultResponse; + } +} 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 000000000..7ee7f5f10 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestRunner.java @@ -0,0 +1,60 @@ +package eu.functionizer.functionizertestingtool.service; + +import com.microsoft.azure.management.Azure; +import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; + +import eu.functionizer.functionizertestingtool.model.test.TestResultResponse; +import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; + +import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; +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.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.*; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + + +@Service +@Slf4j +@AllArgsConstructor(onConstructor = @__(@Autowired)) +public class TestRunner { + + public AzureFunctionsService azureFunctionsService; + + public String getReport() { + // TODO + return "report"; + } + + + public TestResultResponse runTests() { + + Launcher launcher = LauncherFactory.create(); + LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + .selectors(selectClass(ServerlessFunctionTestFactory.class)) + .build(); + TestPlan testPlan = launcher.discover(request); + + SummaryGeneratingListener listener = new SummaryGeneratingListener(); + launcher.registerTestExecutionListeners(listener); + launcher.execute(request); + + TestExecutionSummary summary = listener.getSummary(); + + PrintWriter printWriter = new PrintWriter(System.out); + summary.printTo(printWriter); + summary.printFailuresTo(printWriter); + return TestResultResponse.fromTestSummary(summary); + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestingService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestingService.java deleted file mode 100644 index a8bdf3b59..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/TestingService.java +++ /dev/null @@ -1,177 +0,0 @@ -package eu.functionizer.functionizertestingtool.service; - -import com.amazonaws.services.lambda.model.InvokeRequest; -import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; -import eu.functionizer.functionizertestingtool.model.test.FunctionTestConfiguration; -import eu.functionizer.functionizertestingtool.model.test.TestCase; -import eu.functionizer.functionizertestingtool.model.test.TestConfiguration; - -import eu.functionizer.functionizertestingtool.communication.cloudiator.CloudiatorApi; -import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; -import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; -import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationService; -import io.github.cloudiator.rest.model.*; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; - -import java.io.*; -import java.lang.Runtime; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; - -@Service -@Slf4j -@AllArgsConstructor(onConstructor = @__(@Autowired)) -public class TestingService { - - AWSLambdaService awsLambdaService; - AzureFunctionsService azureFunctionsService; - CloudiatorApi cloudiatorApi; - TestConfigurationService testConfigurationService; - - private String extractFunctionName(Function function) { - return Objects.requireNonNull(function.getStackId()).split("-")[0]; - } - - private String extractRegionName(Function function) { - String[] locationSplit = Objects.requireNonNull(function.getLocationId()).split("~"); - return locationSplit[locationSplit.length - 1]; - } - - private Map getFunctionsMap() { - List functions = cloudiatorApi.getFunctionList(); - return functions - .stream() - .collect(Collectors.toMap(this::extractFunctionName, function -> function)); - } - - private Map getCloudsMap() { - List clouds = cloudiatorApi.getCloudList(); - return clouds.stream().collect(Collectors.toMap(Cloud::getId, cloud -> cloud)); - } - - private String getTaskInterfaceFunctionName(Task task) { - FaasInterface taskInterface = (FaasInterface) Objects.requireNonNull(task.getInterfaces()).get(0); - return taskInterface.getFunctionName(); - } - - private Map getFunctionSuffices() { - List jobs = cloudiatorApi.getJobList(); - List tasks = jobs.stream() - .map(Job::getTasks) - .filter(Objects::nonNull) - .flatMap(List::stream) - .filter(task -> FaasInterface.class.equals(Objects.requireNonNull(task.getInterfaces()).get(0).getClass())) - .collect(Collectors.toList()); - return tasks.stream().collect(Collectors.toMap(Task::getName, this::getTaskInterfaceFunctionName)); - } - - @Test - public String testFunctions() { - - List functionTests = testConfigurationService.loadTestConfiguration().getTests(); - Map functions = getFunctionsMap(); - Map clouds = getCloudsMap(); - Map functionSuffices = getFunctionSuffices(); - - for (FunctionTestConfiguration functionTestConfiguration : functionTests) { - String functionNameLower = functionTestConfiguration.getFunctionName().toLowerCase(); - - Function function = functions.get(functionNameLower); - Cloud cloud = clouds.get(function.getCloudId()); - String providerName = cloud.getApi().getProviderName(); - - if (AWSLambdaService.providerName.getValue().equals(providerName)) { - - String region = extractRegionName(function); - String functionName = String.join( - "-", - function.getStackId(), - functionSuffices.get(functionTestConfiguration.getFunctionName()) - ); - testAWSLambdaFunction(functionTestConfiguration, cloud.getCredential(), region, functionName); - - } else if (AzureFunctionsService.providerName.getValue().equals(providerName)) { - // TODO - System.out.println("Testing azure function..."); - } - } - return "tests passed"; - } - - @Test - public void testAWSLambdaFunction( - FunctionTestConfiguration testConfiguration, - CloudCredential credential, - String region, - String functionName - ) { - - awsLambdaService.configureClient(credential, region); - - InvokeRequest invokeRequest = new InvokeRequest().withFunctionName(functionName); - - for (TestCase testCase : testConfiguration.getTestCases()) { - invokeRequest.setPayload(testCase.getEvent()); - - String resultString = awsLambdaService.invokeFunction(invokeRequest); - assertEquals(testCase.getExpectedOutput(), resultString); - } - } - - - public List listLambdas() { - return awsLambdaService.listFunctions(); - } - - public String invokeLambdaFunction(InvokeFunctionRequest request) { - awsLambdaService.listFunctions(); - String ans = awsLambdaService.invokeFunction(request); - System.out.println(ans); - return ans; - } - - public String invokeAzureFunction(InvokeFunctionRequest request) { - return azureFunctionsService.invokeFunction(request); - } - - - public String executeCommand(String[] command) { - StringBuilder output = new StringBuilder(); - try { - Process process = Runtime.getRuntime().exec(command); - process.waitFor(); - - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line = ""; - while ((line = reader.readLine()) != null) { - output.append(line).append("\n"); - } - } catch (Exception e) { - e.printStackTrace(); - } - return output.toString(); - } - - public String invokeGCloudFunction(InvokeFunctionRequest request) { - String[] command = { - "gcloud", - "functions", - "call", - request.getFunctionName(), - "--data", - request.getPayload() - }; - - return executeCommand(command); - } -} 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 index 171b067b8..cd0bd7420 100644 --- 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 @@ -2,11 +2,9 @@ package eu.functionizer.functionizertestingtool.service.provider; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.auth.profile.ProfileCredentialsProvider; import com.amazonaws.regions.Regions; import com.amazonaws.services.lambda.AWSLambda; import com.amazonaws.services.lambda.AWSLambdaClientBuilder; -import com.amazonaws.services.lambda.model.FunctionConfiguration; import com.amazonaws.services.lambda.model.InvokeRequest; import com.amazonaws.services.lambda.model.InvokeResult; import com.amazonaws.services.lambda.model.ServiceException; @@ -16,55 +14,29 @@ import io.github.cloudiator.rest.model.CloudCredential; import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.stream.Collectors; @Service -public class AWSLambdaService extends ProviderService { - public static Provider providerName = Provider.AWS_LAMBDA; +public class AWSLambdaService { + public static Provider providerName = Provider.AWS; - private AWSLambda awsLambda; + private static AWSLambdaClientBuilder builder = AWSLambdaClientBuilder.standard(); + private static AWSLambda awsLambda; - public void configureClient(CloudCredential cloudCredential, String region) { + public void buildClient(CloudCredential cloudCredential, String region) { BasicAWSCredentials credentials = new BasicAWSCredentials( - cloudCredential.getUser(), cloudCredential.getSecret() + cloudCredential.getUser(), cloudCredential.getSecret() ); - awsLambda = AWSLambdaClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(credentials)) - .withRegion(Regions.fromName(region)) - .build(); + awsLambda = builder + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(Regions.fromName(region)) + .build(); } - public void configureClient() { - awsLambda = AWSLambdaClientBuilder.standard() - .withCredentials(new ProfileCredentialsProvider()) - .withRegion(Regions.EU_WEST_1) - .build(); - } - - - public List listFunctions() { - - List functions = awsLambda.listFunctions().getFunctions(); - - return functions.stream().map(FunctionConfiguration::getFunctionName).collect(Collectors.toList()); - } - - public String invokeFunction(InvokeFunctionRequest request) { + public static String invokeFunction(InvokeFunctionRequest request) { InvokeRequest invokeRequest = new InvokeRequest() - .withFunctionName(request.getFunctionName()) - .withPayload(request.getPayload()); - - try { - InvokeResult result = awsLambda.invoke(invokeRequest); - return new String(result.getPayload().array(), StandardCharsets.UTF_8); - } catch (ServiceException e) { - return null; - } - } - - public String invokeFunction(InvokeRequest invokeRequest) { + .withFunctionName(request.getFunctionName()) + .withPayload(request.getPayload()); try { InvokeResult result = awsLambda.invoke(invokeRequest); 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 index 82319a233..adb42604e 100644 --- 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 @@ -1,35 +1,85 @@ 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.Provider.Provider; -import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; +import io.github.cloudiator.rest.model.CloudCredential; +import io.github.cloudiator.rest.model.Function; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; - +import java.io.IOException; import java.util.Collections; @Service -public class AzureFunctionsService extends ProviderService { +public class AzureFunctionsService { public static Provider providerName = Provider.AZURE; + private final static String ENDPOINT_STATIC = "azurewebsites.net/api"; + private final static String CLIENT_TENANT_DELIMITER = ":"; + + public 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"; + } + + public static Azure buildAzureClient(CloudCredential credential) throws Exception { + String user = credential.getUser(); + 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, + credential.getSecret(), + AzureEnvironment.AZURE + ); + + try { + return Azure.authenticate(credentials).withDefaultSubscription(); + } catch (IOException e) { + e.printStackTrace(); + throw new Exception(":("); + } + } + + public static String getFunctionKey(Azure azureClient, Function function) { + String resourceGroup = buildResourceGroupName(function.getStackId()); + + FunctionApp functionApp = azureClient + .appServices() + .functionApps() + .getByResourceGroup(resourceGroup, function.getStackId()); + + return functionApp.getMasterKey(); + } - public String invokeFunction(InvokeFunctionRequest request){ + public 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", request.getFunctionKey()); + headers.set("x-functions-key", functionKey); - HttpEntity entity = new HttpEntity<>(request.getPayload(), headers); + HttpEntity entity = new HttpEntity<>(input, headers); ResponseEntity response = restTemplate.postForEntity( - request.getFunctionUri(), - entity, - String.class + url, + entity, + String.class ); - return response.toString(); + if (response.getStatusCode() == HttpStatus.OK) { + return response.getBody(); + } else { + throw new Exception("Not 200 response"); + } } } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/GoogleCloudFunctionsService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/GoogleCloudFunctionsService.java deleted file mode 100644 index 96c0a2b88..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/GoogleCloudFunctionsService.java +++ /dev/null @@ -1,16 +0,0 @@ -package eu.functionizer.functionizertestingtool.service.provider; - -import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; -import org.springframework.stereotype.Service; -import com.google.api.services.cloudfunctions.v1.CloudFunctions; - -@Service -public class GoogleCloudFunctionsService { - - private CloudFunctions cloudFunctions; - - public String invokeFunction(InvokeFunctionRequest request) { - - return "Ok"; - } -} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/ProviderService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/ProviderService.java deleted file mode 100644 index 044158acc..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/ProviderService.java +++ /dev/null @@ -1,11 +0,0 @@ -package eu.functionizer.functionizertestingtool.service.provider; - -import eu.functionizer.functionizertestingtool.model.Provider.Provider; -import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; - -public abstract class ProviderService { - - public static Provider providerName; - - abstract public Object invokeFunction(InvokeFunctionRequest request); -} 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 000000000..53ca509fd --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/ServerlessFunctionTestFactory.java @@ -0,0 +1,258 @@ +package eu.functionizer.functionizertestingtool.service.test; + +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 com.amazonaws.services.lambda.model.ServiceException; + + +import com.microsoft.azure.management.Azure; +import eu.functionizer.functionizertestingtool.model.test.FunctionTestConfiguration; +import eu.functionizer.functionizertestingtool.model.test.TestCase; +import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; +import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; +import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; +import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoader; +import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; +import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorClientApi; +import eu.melodic.upperware.guibackend.communication.cloudiator.QueueInspector; +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 org.junit.jupiter.api.*; + + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + +public class ServerlessFunctionTestFactory { + + private static final String CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") + + "/eu.melodic.cloudiator-client.properties"; + + private static CloudiatorApi cloudiatorApi; + private static MatchmakingApi matchmakingApi; + + + @BeforeAll + static void setUp() throws IOException { + Properties properties = new Properties(); + properties.load(new FileInputStream(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); + } + + + private static String extractTaskName(Function function) { + return Objects.requireNonNull(function.getStackId()).split("-")[0]; + } + + private static String getTaskInterfaceFunctionName(Task task) { + FaasInterface taskInterface = (FaasInterface) Objects.requireNonNull(task.getInterfaces()).get(0); + return taskInterface.getFunctionName(); + } + + private Map getFunctionsMap() { + List functions = cloudiatorApi.getFunctionList(); + return functions + .stream() + .filter(function -> !Objects.requireNonNull(function.getStackId()).isEmpty()) + .collect(Collectors.toMap(ServerlessFunctionTestFactory::extractTaskName, function -> function)); + } + + private Map getCloudsMap() { + List clouds = cloudiatorApi.getCloudList(); + return clouds.stream().collect(Collectors.toMap(Cloud::getId, cloud -> cloud)); + } + + private Map getFunctionNames() { + List jobs = cloudiatorApi.getJobList(); + return jobs + .stream() + .map(Job::getTasks) + .filter(Objects::nonNull) + .flatMap(List::stream) + .filter( + task -> FaasInterface.class.equals( + Objects.requireNonNull(task.getInterfaces()).get(0).getClass() + ) + ) + .collect(Collectors.toMap( + Task::getName, + ServerlessFunctionTestFactory::getTaskInterfaceFunctionName + )); + } + + private Map getLocations() { + List faasNodes = cloudiatorApi.getFaasFromNodeList(); + + return faasNodes.stream() + .collect(Collectors.toMap( + Node::getOriginId, + node -> { + try { + return Objects.requireNonNull( + matchmakingApi + .getNodeCandidate(node.getNodeCandidate()) + .getLocation() + ).getName(); + } catch (ApiException e) { + e.printStackTrace(); + } + return null; + } + )); + } + + + @TestFactory + public Collection dynamicTestCollection() throws Exception { + List functionTests = TestConfigurationLoader.loadTestConfiguration().getTests(); + Map functions = getFunctionsMap(); + Map clouds = getCloudsMap(); + Map functionNames = getFunctionNames(); + Map locations = getLocations(); + + List dynamicTests = new ArrayList<>(); + + for (FunctionTestConfiguration functionTestConfiguration : functionTests) { + String canonicalFunctionName = functionTestConfiguration.getFunctionName(); + String functionIdentifier = canonicalFunctionName.toLowerCase(); + String functionName = functionNames.get(canonicalFunctionName); + + Function function = functions.get(functionIdentifier); + if (function == null) { + continue; // just don't test if not found TODO react + } + + Cloud cloud = clouds.get(function.getCloudId()); + String providerName = cloud.getApi().getProviderName(); + + if (AWSLambdaService.providerName.getValue().equals(providerName)) { + + String awsFunctionName = String.join( + "-", + function.getStackId(), + functionName + ); + + AWSLambda awsLambdaClient = buildAWSLambdaClient( + cloud.getCredential(), + locations.get(function.getId()) + ); + + functionTestConfiguration.getTestCases().forEach(testCase -> { + testCase.setFunctionName(awsFunctionName); + + dynamicTests.add(DynamicTest.dynamicTest( + "TEST FOR " + + functionName + + ": EVENT=" + testCase.getEvent() + + ", EXPECTED OUTPUT=" + + testCase.getExpectedOutput(), + () -> executeAWSLambdaTestCase(awsLambdaClient, testCase) + )); + }); + + } else if (AzureFunctionsService.providerName.getValue().equals(providerName)) { + Azure azureClient = AzureFunctionsService.buildAzureClient(cloud.getCredential()); + String functionKey = AzureFunctionsService.getFunctionKey(azureClient, function); + String azureFunctionEndpoint = AzureFunctionsService.buildFunctionUrl( + function.getStackId(), + functionTestConfiguration.getTriggerPath() + ); + + functionTestConfiguration.getTestCases().forEach(testCase -> { + testCase.setFunctionName(functionName); // TODO maybe modified and where to get function key? + + dynamicTests.add(DynamicTest.dynamicTest( + "TEST FOR " + + functionName + + ": EVENT=" + testCase.getEvent() + + ", EXPECTED OUTPUT=" + + testCase.getExpectedOutput(), + () -> executeAzureTestCase(azureFunctionEndpoint, functionKey, testCase) + )); + }); + } + } + return dynamicTests; + } + + public static AWSLambda buildAWSLambdaClient(CloudCredential cloudCredential, String location) { + BasicAWSCredentials credentials = new BasicAWSCredentials( + cloudCredential.getUser(), cloudCredential.getSecret() + ); + + return AWSLambdaClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(Regions.fromName(location)) + .build(); + } + + public void executeAWSLambdaTestCase(AWSLambda awsLambda, TestCase testCase) { + InvokeRequest invokeRequest = new InvokeRequest() + .withFunctionName(testCase.getFunctionName()) + .withPayload(testCase.getEvent()); + + String resultString = null; + + try { + InvokeResult result = awsLambda.invoke(invokeRequest); + resultString = new String(result.getPayload().array(), StandardCharsets.UTF_8); + } catch (ServiceException e) { + fail("An exception while fetching response from AWS Lambda Client occurred"); + } + assertEquals(testCase.getExpectedOutput(), resultString); + } + + public void executeAzureTestCase( + String endpoint, + String functionKey, + TestCase testCase + ) throws Exception { + String response = AzureFunctionsService.invokeFunction( + endpoint, + functionKey, + testCase.getEvent() + ); + assertEquals(testCase.getExpectedOutput(), response); + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationService.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationLoader.java similarity index 71% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationService.java rename to functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationLoader.java index d36ec1d23..3a14ffd89 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationService.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationLoader.java @@ -12,11 +12,11 @@ import java.io.*; @Service @Slf4j -public class TestConfigurationService { +public class TestConfigurationLoader { private final static String CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") + "/tests.yml"; - public TestConfiguration loadTestConfiguration() { + public static TestConfiguration loadTestConfiguration() { Yaml yaml = new Yaml(new Constructor(TestConfiguration.class)); try (FileInputStream fileInputStream = new FileInputStream(new File(CONFIG_FILE_PATH))) { @@ -32,19 +32,7 @@ public class TestConfigurationService { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); } } - -// public void updateCloudDefinitionInYamlFile(List cloudDefinitionsForAllProviders) { -// TestConfiguration testConfiguration = loadTestConfiguration(); -// testConfiguration.setCloudDefinitions(cloudDefinitionsForAllProviders); -// saveTestConfiguration(testConfiguration); -// } -// -// public void updateByonDefinitionInYamlFile(List byonDefinitionsList) { -// TestConfiguration testConfiguration = loadTestConfiguration(); -// testConfiguration.setByonDefinitions(byonDefinitionsList); -// saveTestConfiguration(testConfiguration); -// } - + private void saveTestConfiguration(TestConfiguration testConfiguration) { Yaml yaml = new Yaml(); FileWriter writer = null; diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/AnotherTest.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/AnotherTest.java deleted file mode 100644 index af4c79292..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/AnotherTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package eu.functionizer.functionizertestingtool.test; - -import static org.assertj.core.api.Assertions.*; - -import java.net.URL; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.http.ResponseEntity; - - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class AnotherTest { - - @LocalServerPort - private int port; - - private URL base; - - @Autowired - private TestRestTemplate template; - - @BeforeEach - public void setUp() throws Exception { - this.base = new URL("http://localhost:" + port + "/"); - } - - @Test - public void getHello() throws Exception { - ResponseEntity response = template.getForEntity(base.toString(), String.class); - assertThat(response.getBody()).isEqualTo("Hello"); - } -} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/FunctionizerFunctionsTest.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/FunctionizerFunctionsTest.java deleted file mode 100644 index 1d204c603..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/test/FunctionizerFunctionsTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package eu.functionizer.functionizertestingtool.test; - - -import eu.functionizer.functionizertestingtool.model.test.FunctionTestConfiguration; -import eu.functionizer.functionizertestingtool.model.test.TestConfiguration; -import org.junit.jupiter.api.Test; - -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.springframework.boot.test.context.SpringBootTest; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; - -import java.io.InputStream; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -@SpringBootTest -@RunWith(Parameterized.class) -public class FunctionizerFunctionsTest { - - public static TestConfiguration loadTestConfiguration() { - Yaml yaml = new Yaml(new Constructor(TestConfiguration.class)); - InputStream configFile = FunctionizerFunctionsTest.class - .getClassLoader() - .getResourceAsStream("tests.yml"); - return yaml.load(configFile); - } - - @Parameters - public static List data() { - return loadTestConfiguration().getTests(); - } - - private String functionName; - private String event; - private String expectedOutput; - - public FunctionizerFunctionsTest(String functionName, String event, String expectedOutput) { - this.functionName = functionName; - this.event = event; - this.expectedOutput = expectedOutput; - } - - @Test - public void TestFunctionOutput() { - } - -} diff --git a/functionizer-testing-tool/src/test/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplicationTests.java b/functionizer-testing-tool/src/test/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplicationTests.java deleted file mode 100644 index 9d6785e97..000000000 --- a/functionizer-testing-tool/src/test/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package eu.functionizer.functionizertestingtool; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class FunctionizerTestingToolApplicationTests { - - @Test - void contextLoads() { - } - -} -- GitLab From 1551fd74fc974c0b23847caabd902142d1c23bcd Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Thu, 14 May 2020 17:42:26 +0200 Subject: [PATCH 03/18] functionizer testing tool report data draft --- functionizer-testing-tool/pom.xml | 9 +- .../FunctionizerTestingToolController.java | 15 +- .../TestingToolContext.java | 158 +++++------ .../model/Provider/Provider.java | 17 -- .../model/invoke/InvokeFunctionRequest.java | 12 - .../model/test/TestResultResponse.java | 55 ---- .../security/WebSecurity.java | 18 +- .../service/TestRunner.java | 35 ++- .../service/provider/AWSLambdaService.java | 11 +- .../provider/AzureFunctionsService.java | 10 +- .../service/test/FunctionizerReportData.java | 229 ++++++++++++++++ ...ctionizerTestReportGeneratingListener.java | 129 +++++++++ .../test/ServerlessFunctionTestFactory.java | 258 ++++++++++++------ .../service/yaml/TestConfigurationLoader.java | 17 +- .../secure/store/SecureStoreService.java | 9 +- 15 files changed, 677 insertions(+), 305 deletions(-) rename functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/{controller => }/FunctionizerTestingToolController.java (63%) delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/invoke/InvokeFunctionRequest.java delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestResultResponse.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerReportData.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestReportGeneratingListener.java diff --git a/functionizer-testing-tool/pom.xml b/functionizer-testing-tool/pom.xml index e8ba2d706..94707f993 100644 --- a/functionizer-testing-tool/pom.xml +++ b/functionizer-testing-tool/pom.xml @@ -136,16 +136,17 @@ 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 + org.jetbrains annotations diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java similarity index 63% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java rename to functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java index 2a43840f8..888093d7d 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/controller/FunctionizerTestingToolController.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java @@ -1,14 +1,14 @@ -package eu.functionizer.functionizertestingtool.controller; +package eu.functionizer.functionizertestingtool; -import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; -import eu.functionizer.functionizertestingtool.model.test.TestResultResponse; import eu.functionizer.functionizertestingtool.service.TestRunner; +import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; import lombok.AllArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import java.util.List; +import java.util.Collection; +import java.util.Map; @RestController @AllArgsConstructor(onConstructor = @__(@Autowired)) @@ -28,13 +28,8 @@ public class FunctionizerTestingToolController { @PostMapping("/test") @ResponseStatus(HttpStatus.OK) - public TestResultResponse runTests() { + public Collection runTests() { return testRunner.runTests(); } - @RequestMapping("/report") - @ResponseStatus(HttpStatus.OK) - public String getTestReport() { - return testRunner.getReport(); - } } 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 index 76f066a6b..931a3ba85 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java @@ -18,87 +18,87 @@ import org.springframework.web.client.RestTemplate; @Configuration public class TestingToolContext { - @Bean - public RestTemplate getRestTemplate() { - - SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); - simpleClientHttpRequestFactory.setConnectTimeout(180000); - simpleClientHttpRequestFactory.setReadTimeout(120000); - - return new RestTemplate(simpleClientHttpRequestFactory); - } - - @Bean - public ApiClient apiClient(CloudiatorProperties cloudiatorProperties) { - ApiClient apiClient = new ApiClient(); - apiClient.setBasePath(cloudiatorProperties.getCloudiator().getUrl()); - apiClient.setApiKey(cloudiatorProperties.getCloudiator().getApiKey()); - apiClient.setReadTimeout(cloudiatorProperties.getCloudiator().getHttpReadTimeout()); - return apiClient; - } - - @Bean - public CloudApi cloudApi(ApiClient apiClient) { - return new CloudApi(apiClient); - } - - @Bean - public SecurityApi securityApi(ApiClient apiClient) { - return new SecurityApi(apiClient); - } - - @Bean - public NodeApi nodeApi(ApiClient apiClient) { - return new NodeApi(apiClient); - } - - @Bean - public QueueApi queueApi(ApiClient apiClient) { - return new QueueApi(apiClient); - } - - @Bean - public ProcessApi processApi(ApiClient apiClient) { - return new ProcessApi((apiClient)); - } - - @Bean - public JobApi jobApi(ApiClient apiClient) { - return new JobApi(apiClient); - } - - @Bean - public MonitoringApi monitoringApi(ApiClient apiClient) { - return new MonitoringApi(apiClient); - } - +// @Bean +// public RestTemplate getRestTemplate() { +// +// SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); +// simpleClientHttpRequestFactory.setConnectTimeout(180000); +// simpleClientHttpRequestFactory.setReadTimeout(120000); +// +// return new RestTemplate(simpleClientHttpRequestFactory); +// } +// +// @Bean +// public ApiClient apiClient(CloudiatorProperties cloudiatorProperties) { +// ApiClient apiClient = new ApiClient(); +// apiClient.setBasePath(cloudiatorProperties.getCloudiator().getUrl()); +// apiClient.setApiKey(cloudiatorProperties.getCloudiator().getApiKey()); +// apiClient.setReadTimeout(cloudiatorProperties.getCloudiator().getHttpReadTimeout()); +// return apiClient; +// } +// +// @Bean +// public CloudApi cloudApi(ApiClient apiClient) { +// return new CloudApi(apiClient); +// } +// +// @Bean +// public SecurityApi securityApi(ApiClient apiClient) { +// return new SecurityApi(apiClient); +// } +// +// @Bean +// public NodeApi nodeApi(ApiClient apiClient) { +// return new NodeApi(apiClient); +// } +// +// @Bean +// public QueueApi queueApi(ApiClient apiClient) { +// return new QueueApi(apiClient); +// } +// +// @Bean +// public ProcessApi processApi(ApiClient apiClient) { +// return new ProcessApi((apiClient)); +// } +// +// @Bean +// public JobApi jobApi(ApiClient apiClient) { +// return new JobApi(apiClient); +// } +// +// @Bean +// public MonitoringApi monitoringApi(ApiClient apiClient) { +// return new MonitoringApi(apiClient); +// } +// @Bean public JWTService jWTService(MelodicSecurityProperties melodicSecurityProperties) { return new JWTServiceImpl(melodicSecurityProperties); } - - @Bean - public MatchmakingApi matchmakingApi(ApiClient apiClient) { - return new MatchmakingApi(apiClient); - } - - @Bean - QueueInspector queueInspector(QueueApi queueApi, CloudiatorProperties cloudiatorProperties) { - return new QueueInspector(queueApi, cloudiatorProperties); - } - - @Bean - 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 MatchmakingApi matchmakingApi(ApiClient apiClient) { +// return new MatchmakingApi(apiClient); +// } +// +// @Bean +// QueueInspector queueInspector(QueueApi queueApi, CloudiatorProperties cloudiatorProperties) { +// return new QueueInspector(queueApi, cloudiatorProperties); +// } + +// @Bean +// 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 +// ); +// } } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java deleted file mode 100644 index f46f9b57e..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/Provider/Provider.java +++ /dev/null @@ -1,17 +0,0 @@ -package eu.functionizer.functionizertestingtool.model.Provider; - -public enum Provider { - AWS("aws-ec2"), - AZURE("azure"), - GOOGLE("google-cloud-functions"); - - public final String value; - - Provider(String value) { - this.value = value; - } - - public String getValue() { - return value; - } -} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/invoke/InvokeFunctionRequest.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/invoke/InvokeFunctionRequest.java deleted file mode 100644 index 07147ea68..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/invoke/InvokeFunctionRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package eu.functionizer.functionizertestingtool.model.invoke; - -import lombok.Data; - -@Data -public class InvokeFunctionRequest { - private String functionName; - private String payload; - private String expectedOutput; - private String functionUri; - private String functionKey; -} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestResultResponse.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestResultResponse.java deleted file mode 100644 index 06646ef45..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestResultResponse.java +++ /dev/null @@ -1,55 +0,0 @@ -package eu.functionizer.functionizertestingtool.model.test; - -import lombok.Data; -import org.assertj.core.api.Fail; -import org.junit.Test; -import org.junit.platform.launcher.listeners.TestExecutionSummary; - -import java.util.List; -import java.util.stream.Collectors; - -@Data -public class TestResultResponse { - private long testRunDuration; - - private int totalFound; - private int totalRan; - private int totalSkipped; - - private int totalSucceeded; - private int totalFailed; - - private List failures; - - private static class Failure { - public String displayName; - public String cause; - - public Failure(String displayName, String cause) { - this.displayName = displayName; - this.cause = cause; - } - } - - public static TestResultResponse fromTestSummary(TestExecutionSummary summary) { - TestResultResponse testResultResponse = new TestResultResponse(); - testResultResponse.setTestRunDuration(summary.getTimeFinished() - summary.getTimeStarted()); - testResultResponse.setTotalFound((int) summary.getTestsFoundCount()); - testResultResponse.setTotalRan((int) summary.getTestsStartedCount()); - testResultResponse.setTotalSkipped((int) summary.getTestsSkippedCount()); - testResultResponse.setTotalSucceeded((int) summary.getTestsSucceededCount()); - testResultResponse.setTotalFailed((int) summary.getTestsFailedCount()); - - testResultResponse.setFailures( - summary - .getFailures() - .stream() - .map(failure -> new Failure( - failure.getTestIdentifier().getDisplayName(), - failure.getException().getMessage() - )) - .collect(Collectors.toList()) - ); - return testResultResponse; - } -} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java index 77747a72d..b86ffa7a8 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java @@ -1,6 +1,5 @@ package eu.functionizer.functionizertestingtool.security; - import eu.paasage.upperware.security.authapi.JWTAuthorizationFilter; import eu.paasage.upperware.security.authapi.token.JWTService; import lombok.RequiredArgsConstructor; @@ -31,18 +30,21 @@ public class WebSecurity extends WebSecurityConfigurerAdapter { if (securityEnabled) { log.info("Running WITH security"); http.csrf().disable().antMatcher("/auth/**") - .authorizeRequests() - .anyRequest().authenticated() - .and() - .addFilterBefore(new JWTAuthorizationFilter(authenticationManager(), jwtService), BasicAuthenticationFilter.class); + .authorizeRequests() + .anyRequest().authenticated() + .and() + .addFilterBefore( + new JWTAuthorizationFilter(authenticationManager(), jwtService), + BasicAuthenticationFilter.class + ); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } else { log.info("Running WITHOUT security"); http.csrf().disable() - .authorizeRequests() - .antMatchers("/**").permitAll() - .anyRequest().authenticated(); + .authorizeRequests() + .antMatchers("/**").permitAll() + .anyRequest().authenticated(); } } } 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 index 7ee7f5f10..87f9dd611 100644 --- 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 @@ -1,15 +1,17 @@ package eu.functionizer.functionizertestingtool.service; -import com.microsoft.azure.management.Azure; -import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; - -import eu.functionizer.functionizertestingtool.model.test.TestResultResponse; -import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Map; import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; +import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; +import eu.functionizer.functionizertestingtool.service.test.FunctionizerTestReportGeneratingListener; 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; @@ -17,11 +19,11 @@ import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; +import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.io.*; - import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; @@ -32,13 +34,7 @@ public class TestRunner { public AzureFunctionsService azureFunctionsService; - public String getReport() { - // TODO - return "report"; - } - - - public TestResultResponse runTests() { + public Collection runTests() { Launcher launcher = LauncherFactory.create(); LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() @@ -47,14 +43,15 @@ public class TestRunner { TestPlan testPlan = launcher.discover(request); SummaryGeneratingListener listener = new SummaryGeneratingListener(); - launcher.registerTestExecutionListeners(listener); + FunctionizerTestReportGeneratingListener myListener = + new FunctionizerTestReportGeneratingListener(); + launcher.registerTestExecutionListeners(listener, myListener); launcher.execute(request); TestExecutionSummary summary = listener.getSummary(); + FunctionizerReportData report = myListener.getReport(); + - PrintWriter printWriter = new PrintWriter(System.out); - summary.printTo(printWriter); - summary.printFailuresTo(printWriter); - return TestResultResponse.fromTestSummary(summary); + return report.getTestTree(); } } 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 index cd0bd7420..24e1c7998 100644 --- 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 @@ -8,8 +8,7 @@ import com.amazonaws.services.lambda.AWSLambdaClientBuilder; import com.amazonaws.services.lambda.model.InvokeRequest; import com.amazonaws.services.lambda.model.InvokeResult; import com.amazonaws.services.lambda.model.ServiceException; -import eu.functionizer.functionizertestingtool.model.Provider.Provider; -import eu.functionizer.functionizertestingtool.model.invoke.InvokeFunctionRequest; +import eu.melodic.upperware.guibackend.model.provider.Provider; import io.github.cloudiator.rest.model.CloudCredential; import org.springframework.stereotype.Service; @@ -17,7 +16,7 @@ import java.nio.charset.StandardCharsets; @Service public class AWSLambdaService { - public static Provider providerName = Provider.AWS; + public final static Provider provider = Provider.AWS_EC2; private static AWSLambdaClientBuilder builder = AWSLambdaClientBuilder.standard(); private static AWSLambda awsLambda; @@ -33,10 +32,10 @@ public class AWSLambdaService { .build(); } - public static String invokeFunction(InvokeFunctionRequest request) { + public static String invokeFunction(String functionName, String payload) { InvokeRequest invokeRequest = new InvokeRequest() - .withFunctionName(request.getFunctionName()) - .withPayload(request.getPayload()); + .withFunctionName(functionName) + .withPayload(payload); try { InvokeResult result = awsLambda.invoke(invokeRequest); 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 index adb42604e..f1728f450 100644 --- 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 @@ -4,7 +4,7 @@ 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.Provider.Provider; +import eu.melodic.upperware.guibackend.model.provider.Provider; import io.github.cloudiator.rest.model.CloudCredential; import io.github.cloudiator.rest.model.Function; import org.springframework.http.*; @@ -15,8 +15,9 @@ import java.io.IOException; import java.util.Collections; @Service + public class AzureFunctionsService { - public static Provider providerName = Provider.AZURE; + public final static Provider provider = Provider.AZURE; private final static String ENDPOINT_STATIC = "azurewebsites.net/api"; private final static String CLIENT_TENANT_DELIMITER = ":"; @@ -28,8 +29,7 @@ public class AzureFunctionsService { return stackId + "group"; } - public static Azure buildAzureClient(CloudCredential credential) throws Exception { - String user = credential.getUser(); + public static Azure buildAzureClient(String user, String secret) throws Exception { int colonIndex = user.lastIndexOf(CLIENT_TENANT_DELIMITER); String clientId = user.substring(0, colonIndex); String tenantId = user.substring(colonIndex + 1); @@ -37,7 +37,7 @@ public class AzureFunctionsService { ApplicationTokenCredentials credentials = new ApplicationTokenCredentials( clientId, tenantId, - credential.getSecret(), + secret, AzureEnvironment.AZURE ); 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 000000000..0c8a650ce --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerReportData.java @@ -0,0 +1,229 @@ +package eu.functionizer.functionizertestingtool.service.test; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import org.junit.Test; +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 java.util.Collections.emptyList; +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; + + private int containersFoundCount; + private int testsFoundCount; + private int finishedTestCount; + private int skippedTestCount; + public long timeFinished; + public int containersFound; + public int testsFound; + public int containersStarted; + public int testsStarted; + public int containersSucceeded; + public int testsSucceeded; + public int containersAborted; + public int testsAborted; + public int containersFailed; + public int testsFailed; + private List failures = new ArrayList<>(); + + private Map testTree = new ConcurrentHashMap<>(); + private Map testsResults = new ConcurrentHashMap<>(); + + 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 final Map> reportEntries = new ConcurrentHashMap<>(); + + private final TestPlan testPlan; + private final Clock clock; + + FunctionizerReportData(TestPlan testPlan, Clock clock) { + this.testPlan = testPlan; + this.clock = clock; + + containersFoundCount = 0; + testsFoundCount = 0; + finishedTestCount = 0; + skippedTestCount = 0; + containersFound = 0; + testsFound = 0; + containersStarted = 0; + testsStarted = 0; + containersSucceeded = 0; + testsSucceeded = 0; + containersAborted = 0; + testsAborted = 0; + containersFailed = 0; + testsFailed = 0; + + } + + TestPlan getTestPlan() { + return this.testPlan; + } + + Clock getClock() { + return this.clock; + } + + void markSkipped(TestIdentifier testIdentifier, String reason) { + this.skippedTests.put(testIdentifier, reason == null ? "" : reason); + skippedTestCount += 1L; + } + + 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); + this.finishedTestCount += 1L; + this.testTree.put(testIdentifier.getDisplayName(), result.getStatus().name()); + this.testsResults.put(testIdentifier.getUniqueId(), new TestTreeEntity(testIdentifier, result)); + } + } + + void addReportEntry(TestIdentifier testIdentifier, ReportEntry entry) { + List entries = this.reportEntries.computeIfAbsent(testIdentifier, key -> new ArrayList<>()); + entries.add(entry); + } + + 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; + } + } + + boolean wasSkipped(TestIdentifier testIdentifier) { + return findSkippedAncestor(testIdentifier).isPresent(); + } + + TestExecutionResult getResult(TestIdentifier testIdentifier) { + if (this.finishedTests.containsKey(testIdentifier)) { + return this.finishedTests.get(testIdentifier); + } + Optional parent = this.testPlan.getParent(testIdentifier); + Optional ancestor = findAncestor(parent, 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; + } + + List getReportEntries(TestIdentifier testIdentifier) { + return this.reportEntries.getOrDefault(testIdentifier, emptyList()); + } + + private Optional findSkippedAncestor(TestIdentifier testIdentifier) { + return findAncestor(Optional.of(testIdentifier), this.skippedTests::containsKey); + } + + private Optional findAncestor(Optional testIdentifier, + Predicate predicate) { + Optional current = testIdentifier; + while (current.isPresent()) { + if (predicate.test(current.get())) { + return current; + } + current = this.testPlan.getParent(current.get()); + } + return Optional.empty(); + } + + public void addChildrenToTree(TestTreeEntity entity, TestIdentifier parent) { + testPlan.getChildren(parent).forEach(child -> { + TestTreeEntity childEntity = new TestTreeEntity(child, getResult(child)); + entity.addChild(childEntity); + addChildrenToTree(childEntity, child); + } + ); + } + + public Collection getTestTree() { + List testTreeEntities = new ArrayList<>(); + testPlan.getRoots().forEach(testIdentifier -> { + TestTreeEntity entity = new TestTreeEntity(testIdentifier, getResult(testIdentifier)); + testTreeEntities.add(entity); + addChildrenToTree(entity, testIdentifier); + }); +// testsResults.forEach( +// (id, entity) -> testsResults.forEach( +// (innerId, innerEntity) -> { +// Optional parentId = innerEntity.identifier.getParentId(); +// if (parentId.isPresent()) { +// if (id.equals(parentId.get())) { +// entity.addChild(innerEntity); +// } +// } +// } +// ) +// ); +// Map finalNewTestsResults = testsResults; +// testsResults.forEach((id, entity) -> { +// if (entity.identifier.getParentId().isPresent()) { +// finalNewTestsResults.remove(id); +// } +// }); + + return testTreeEntities; + } + + @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) + public class TestTreeEntity { + private String displayName; + private String status; + private String message = ""; + private double duration; + private List children; + + public TestTreeEntity(TestIdentifier identifier, TestExecutionResult result) { + this.displayName = identifier.getDisplayName(); + this.status = result.getStatus().toString(); + result.getThrowable().ifPresent(value -> this.message = value.getMessage()); + this.duration = getDurationInSeconds(identifier); + this.children = new ArrayList<>(); + } + + public void addChild(TestTreeEntity child) { + this.children.add(child); + } + + } +} diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestReportGeneratingListener.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestReportGeneratingListener.java new file mode 100644 index 000000000..cf7e823b4 --- /dev/null +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestReportGeneratingListener.java @@ -0,0 +1,129 @@ +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; +import java.util.stream.Stream; + +import static java.util.stream.Stream.concat; + +public class FunctionizerTestReportGeneratingListener implements TestExecutionListener { + + private final Clock clock; + + private TestPlan testPlan; + + private FunctionizerReportData reportData; + + public FunctionizerTestReportGeneratingListener() { + this(Clock.systemDefaultZone()); + } + + private FunctionizerTestReportGeneratingListener(Clock clock) { + this.clock = clock; + } + + public FunctionizerReportData getReport() { + return this.reportData; + } + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + this.testPlan = testPlan; + this.reportData = new FunctionizerReportData(testPlan, clock); + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + this.reportData.timeFinished = System.currentTimeMillis(); + } + + @Override + public void dynamicTestRegistered(TestIdentifier testIdentifier) { + if (testIdentifier.isContainer()) { + this.reportData.containersFound += 1; + } + if (testIdentifier.isTest()) { + this.reportData.testsFound += 1; + } + } + + @Override + public void executionSkipped(TestIdentifier testIdentifier, String reason) { + // @formatter:off + long skippedContainers = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) + .filter(TestIdentifier::isContainer) + .count(); + long skippedTests = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) + .filter(TestIdentifier::isTest) + .count(); + // @formatter:on + + this.reportData.markSkipped(testIdentifier, reason); + } + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + if (testIdentifier.isContainer()) { + this.reportData.containersStarted += 1; + } + if (testIdentifier.isTest()) { + this.reportData.testsStarted += 1; + } + 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: { + if (testIdentifier.isContainer()) { + this.reportData.containersSucceeded += 1; + } + if (testIdentifier.isTest()) { + this.reportData.testsSucceeded += 1; + } + break; + } + + case ABORTED: { + if (testIdentifier.isContainer()) { + this.reportData.containersAborted += 1; + } + if (testIdentifier.isTest()) { + this.reportData.testsAborted += 1; + } + break; + } + + case FAILED: { + if (testIdentifier.isContainer()) { + this.reportData.containersFailed += 1; + } + if (testIdentifier.isTest()) { + this.reportData.testsFailed += 1; + } + 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 index 53ca509fd..9a2605e13 100644 --- 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 @@ -1,5 +1,12 @@ package eu.functionizer.functionizertestingtool.service.test; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import java.util.*; +import java.util.stream.Collectors; + import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.regions.Regions; @@ -9,32 +16,40 @@ import com.amazonaws.services.lambda.model.InvokeRequest; import com.amazonaws.services.lambda.model.InvokeResult; import com.amazonaws.services.lambda.model.ServiceException; - import com.microsoft.azure.management.Azure; + import eu.functionizer.functionizertestingtool.model.test.FunctionTestConfiguration; import eu.functionizer.functionizertestingtool.model.test.TestCase; import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; -import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoader; import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorClientApi; import eu.melodic.upperware.guibackend.communication.cloudiator.QueueInspector; +import eu.melodic.upperware.guibackend.controller.deployment.common.SecureVariable; +import eu.melodic.upperware.guibackend.service.secure.store.SecureStoreService; 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.api.CloudApi; +import io.github.cloudiator.rest.api.JobApi; +import io.github.cloudiator.rest.api.MatchmakingApi; +import io.github.cloudiator.rest.api.MonitoringApi; +import io.github.cloudiator.rest.api.NodeApi; +import io.github.cloudiator.rest.api.ProcessApi; +import io.github.cloudiator.rest.api.QueueApi; +import io.github.cloudiator.rest.api.SecurityApi; + import io.github.cloudiator.rest.model.*; + +import org.junit.Ignore; import org.junit.jupiter.api.*; +import org.springframework.web.server.ResponseStatusException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.stream.Collectors; - -import static org.junit.Assert.*; public class ServerlessFunctionTestFactory { @@ -43,17 +58,24 @@ public class ServerlessFunctionTestFactory { private static CloudiatorApi cloudiatorApi; private static MatchmakingApi matchmakingApi; + private static SecureStoreService secureStoreService; + + private TestInfo testInfo; + private TestReporter testReporter; @BeforeAll static void setUp() throws IOException { + Properties properties = new Properties(); properties.load(new FileInputStream(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"))); + cloudiator.setHttpReadTimeout( + Integer.parseInt(properties.getProperty("cloudiator.httpReadTimeout")) + ); CloudiatorProperties cloudiatorProperties = new CloudiatorProperties(); cloudiatorProperties.setCloudiator(cloudiator); @@ -77,6 +99,13 @@ public class ServerlessFunctionTestFactory { ); matchmakingApi = new MatchmakingApi(apiClient); + secureStoreService = new SecureStoreService(cloudiatorApi); + } + + @BeforeEach + void initTestCase(TestInfo testInfo, TestReporter testReporter) { + this.testInfo = testInfo; + this.testReporter = testReporter; } @@ -85,7 +114,9 @@ public class ServerlessFunctionTestFactory { } private static String getTaskInterfaceFunctionName(Task task) { - FaasInterface taskInterface = (FaasInterface) Objects.requireNonNull(task.getInterfaces()).get(0); + FaasInterface taskInterface = (FaasInterface) Objects + .requireNonNull(task.getInterfaces()) + .get(0); return taskInterface.getFunctionName(); } @@ -94,7 +125,10 @@ public class ServerlessFunctionTestFactory { return functions .stream() .filter(function -> !Objects.requireNonNull(function.getStackId()).isEmpty()) - .collect(Collectors.toMap(ServerlessFunctionTestFactory::extractTaskName, function -> function)); + .collect(Collectors.toMap( + ServerlessFunctionTestFactory::extractTaskName, + function -> function + )); } private Map getCloudsMap() { @@ -120,106 +154,158 @@ public class ServerlessFunctionTestFactory { )); } - private Map getLocations() { + private Map getNodeCandidates() { List faasNodes = cloudiatorApi.getFaasFromNodeList(); - return faasNodes.stream() .collect(Collectors.toMap( Node::getOriginId, node -> { try { - return Objects.requireNonNull( - matchmakingApi - .getNodeCandidate(node.getNodeCandidate()) - .getLocation() - ).getName(); + return matchmakingApi.getNodeCandidate(node.getNodeCandidate()); } catch (ApiException e) { e.printStackTrace(); + return null; } - return null; } )); } - @TestFactory - public Collection dynamicTestCollection() throws Exception { - List functionTests = TestConfigurationLoader.loadTestConfiguration().getTests(); - Map functions = getFunctionsMap(); - Map clouds = getCloudsMap(); - Map functionNames = getFunctionNames(); - Map locations = getLocations(); + public Collection dynamicTestNodes() { + testReporter.publishEntry("Collecting test cases..."); + + List dynamicNodes = new ArrayList<>(); + List functionTests; - List dynamicTests = new ArrayList<>(); + try { + functionTests = TestConfigurationLoader + .loadTestConfiguration() + .getTests(); + } catch (ResponseStatusException e) { + testReporter.publishEntry( + "An error occurred while loading test configuration: " + e.getMessage() + ); + return dynamicNodes; + } + + Map functions; + Map functionNames; + Map nodeCandidates; + + try { + functions = getFunctionsMap(); + functionNames = getFunctionNames(); + nodeCandidates = getNodeCandidates(); + } catch (Exception e) { + testReporter.publishEntry( + "An error occurred while fetching data from Cloudiator: " + e.getMessage() + ); + return dynamicNodes; + } for (FunctionTestConfiguration functionTestConfiguration : functionTests) { String canonicalFunctionName = functionTestConfiguration.getFunctionName(); String functionIdentifier = canonicalFunctionName.toLowerCase(); String functionName = functionNames.get(canonicalFunctionName); - Function function = functions.get(functionIdentifier); - if (function == null) { - continue; // just don't test if not found TODO react - } - - Cloud cloud = clouds.get(function.getCloudId()); - String providerName = cloud.getApi().getProviderName(); - - if (AWSLambdaService.providerName.getValue().equals(providerName)) { + List functionNode = new ArrayList<>(); - String awsFunctionName = String.join( - "-", - function.getStackId(), - functionName - ); - - AWSLambda awsLambdaClient = buildAWSLambdaClient( - cloud.getCredential(), - locations.get(function.getId()) - ); + Function function = functions.get(functionIdentifier); - functionTestConfiguration.getTestCases().forEach(testCase -> { - testCase.setFunctionName(awsFunctionName); - - dynamicTests.add(DynamicTest.dynamicTest( - "TEST FOR " - + functionName - + ": EVENT=" + testCase.getEvent() - + ", EXPECTED OUTPUT=" - + testCase.getExpectedOutput(), - () -> executeAWSLambdaTestCase(awsLambdaClient, testCase) - )); - }); - - } else if (AzureFunctionsService.providerName.getValue().equals(providerName)) { - Azure azureClient = AzureFunctionsService.buildAzureClient(cloud.getCredential()); - String functionKey = AzureFunctionsService.getFunctionKey(azureClient, function); - String azureFunctionEndpoint = AzureFunctionsService.buildFunctionUrl( - function.getStackId(), - functionTestConfiguration.getTriggerPath() + if (function == null) { + testReporter.publishEntry( + "Function: " + functionIdentifier + " not found among deployed functions. Omitting" ); + functionNode.add(DynamicTest.dynamicTest("Ignored", this::ignoredTestCase)); + } else { + + NodeCandidate nodeCandidate = nodeCandidates.get(function.getId()); + String user = nodeCandidate.getCloud() + .getCredential() + .getUser(); +// String secretLabel = secureStoreService.createKeyLabelForSecret( +// nodeCandidate.getCloud().getApi().getProviderName(), +// user +// ).getRight(); + String secretLabel = secureStoreService.createKeyLabelForSecret( + nodeCandidate.getCloud().getApi().getProviderName(), + "client" + ).getRight(); + + String secret = secureStoreService.getValueForSecureVariableLabel(secretLabel); + String providerName = nodeCandidate.getCloud().getApi().getProviderName(); + + if (AWSLambdaService.provider.value.equals(providerName)) { + + String awsFunctionName = String.join( + "-", + function.getStackId(), + functionName + ); + + AWSLambda awsLambdaClient = buildAWSLambdaClient( + user, + secret, + nodeCandidate.getLocation().getName() + ); + + functionTestConfiguration.getTestCases().forEach(testCase -> { + testCase.setFunctionName(awsFunctionName); + + functionNode.add(DynamicTest.dynamicTest( + createTestCaseDisplayName( + functionName, + testCase.getEvent(), + testCase.getExpectedOutput() + ), + () -> executeAWSLambdaTestCase(awsLambdaClient, testCase) + )); + }); + + } else if (AzureFunctionsService.provider.value.equals(providerName)) { + Azure azureClient; + String functionKey; + try { + azureClient = AzureFunctionsService.buildAzureClient(user, secret); + functionKey = AzureFunctionsService.getFunctionKey(azureClient, function); + } catch (Exception e) { + testReporter.publishEntry( + "An error occurred while building Azure Client: " + e.getMessage() + ); + continue; + } - functionTestConfiguration.getTestCases().forEach(testCase -> { - testCase.setFunctionName(functionName); // TODO maybe modified and where to get function key? - - dynamicTests.add(DynamicTest.dynamicTest( - "TEST FOR " - + functionName - + ": EVENT=" + testCase.getEvent() - + ", EXPECTED OUTPUT=" - + testCase.getExpectedOutput(), - () -> executeAzureTestCase(azureFunctionEndpoint, functionKey, testCase) - )); - }); + String azureFunctionEndpoint = AzureFunctionsService.buildFunctionUrl( + function.getStackId(), + functionTestConfiguration.getTriggerPath() + ); + + functionTestConfiguration.getTestCases().forEach(testCase -> { + testCase.setFunctionName(functionName); + + functionNode.add(DynamicTest.dynamicTest( + createTestCaseDisplayName( + functionName, + testCase.getEvent(), + testCase.getExpectedOutput() + ), + () -> executeAzureTestCase(azureFunctionEndpoint, functionKey, testCase) + )); + }); + } } + dynamicNodes.add(DynamicContainer.dynamicContainer(functionTestConfiguration.getFunctionName(), functionNode)); } - return dynamicTests; + return dynamicNodes; } - public static AWSLambda buildAWSLambdaClient(CloudCredential cloudCredential, String location) { - BasicAWSCredentials credentials = new BasicAWSCredentials( - cloudCredential.getUser(), cloudCredential.getSecret() - ); + + public static String createTestCaseDisplayName(String functionName, String input, String expectedOutput) { + return "EVENT=" + input + ", EXPECTED OUTPUT=" + expectedOutput; + } + + public static AWSLambda buildAWSLambdaClient(String user, String secret, String location) { + BasicAWSCredentials credentials = new BasicAWSCredentials(user, secret); return AWSLambdaClientBuilder.standard() .withCredentials(new AWSStaticCredentialsProvider(credentials)) @@ -228,6 +314,7 @@ public class ServerlessFunctionTestFactory { } public void executeAWSLambdaTestCase(AWSLambda awsLambda, TestCase testCase) { + testReporter.publishEntry("bleble"); InvokeRequest invokeRequest = new InvokeRequest() .withFunctionName(testCase.getFunctionName()) .withPayload(testCase.getEvent()); @@ -255,4 +342,7 @@ public class ServerlessFunctionTestFactory { ); assertEquals(testCase.getExpectedOutput(), response); } + + @Ignore("Ignored test case") + public void ignoredTestCase() {} } 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 index 3a14ffd89..64267bef1 100644 --- 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 @@ -14,25 +14,32 @@ import java.io.*; @Slf4j public class TestConfigurationLoader { - private final static String CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") + "/tests.yml"; + private final static String CONFIG_FILE_PATH = + System.getenv("MELODIC_CONFIG_DIR") + "/tests.yml"; - public static TestConfiguration loadTestConfiguration() { + public static TestConfiguration loadTestConfiguration() throws ResponseStatusException { Yaml yaml = new Yaml(new Constructor(TestConfiguration.class)); try (FileInputStream fileInputStream = new FileInputStream(new File(CONFIG_FILE_PATH))) { return yaml.load(fileInputStream); } catch (FileNotFoundException e) { - String errorMessage = String.format("File test configuration: %s is missing.", CONFIG_FILE_PATH); + String errorMessage = String.format( + "File test configuration: %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); + 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); } } - + private void saveTestConfiguration(TestConfiguration testConfiguration) { Yaml yaml = new Yaml(); FileWriter writer = null; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/secure/store/SecureStoreService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/secure/store/SecureStoreService.java index 292b63de7..fec820355 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/secure/store/SecureStoreService.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/secure/store/SecureStoreService.java @@ -86,7 +86,14 @@ public class SecureStoreService { } public Pair createKeyLabelForSecret(CloudDefinition cloudDefinition) { - String keyForSecret = cloudDefinition.getApi().getProviderName() + "-" + cloudDefinition.getCredential().getUser() + SECURE_VARIABLE_SECURE_SUFIX; + return createKeyLabelForSecret( + cloudDefinition.getApi().getProviderName(), + cloudDefinition.getCredential().getUser() + ); + } + + public Pair createKeyLabelForSecret(String providerName, String user) { + String keyForSecret = providerName + "-" + user + SECURE_VARIABLE_SECURE_SUFIX; return Pair.of(keyForSecret, SECURE_VARIABLE_PREFIX + keyForSecret + SECURE_VARIABLE_SUFFIX); } -- GitLab From a5eba5fe4060b295683079d92a717c10e4c6ac34 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Mon, 18 May 2020 18:20:30 +0200 Subject: [PATCH 04/18] functionizer testing tool: use batch node candidate fetching --- .../SecureVariableNotFoundException.java | 17 --- .../service/test/FunctionizerReportData.java | 24 +--- .../test/ServerlessFunctionTestFactory.java | 111 +++++++++++++----- 3 files changed, 82 insertions(+), 70 deletions(-) delete mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/exception/SecureVariableNotFoundException.java diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/exception/SecureVariableNotFoundException.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/exception/SecureVariableNotFoundException.java deleted file mode 100644 index e462f37cf..000000000 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/exception/SecureVariableNotFoundException.java +++ /dev/null @@ -1,17 +0,0 @@ -package eu.functionizer.functionizertestingtool.exception; - -import lombok.Getter; -import lombok.Setter; - -import javax.ws.rs.NotFoundException; - -@Getter -@Setter -public class SecureVariableNotFoundException extends NotFoundException { - private String missingVariableName; - - public SecureVariableNotFoundException(String message, String missingVariableName) { - super(message); - this.missingVariableName = missingVariableName; - } -} 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 index 0c8a650ce..1b81cb433 100644 --- 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 @@ -39,8 +39,8 @@ public class FunctionizerReportData { public int testsFailed; private List failures = new ArrayList<>(); - private Map testTree = new ConcurrentHashMap<>(); - private Map testsResults = new ConcurrentHashMap<>(); + private TestTreeEntity testResults; + private final Map finishedTests = new ConcurrentHashMap<>(); private final Map skippedTests = new ConcurrentHashMap<>(); @@ -98,8 +98,6 @@ public class FunctionizerReportData { else { this.finishedTests.put(testIdentifier, result); this.finishedTestCount += 1L; - this.testTree.put(testIdentifier.getDisplayName(), result.getStatus().name()); - this.testsResults.put(testIdentifier.getUniqueId(), new TestTreeEntity(testIdentifier, result)); } } @@ -183,24 +181,6 @@ public class FunctionizerReportData { testTreeEntities.add(entity); addChildrenToTree(entity, testIdentifier); }); -// testsResults.forEach( -// (id, entity) -> testsResults.forEach( -// (innerId, innerEntity) -> { -// Optional parentId = innerEntity.identifier.getParentId(); -// if (parentId.isPresent()) { -// if (id.equals(parentId.get())) { -// entity.addChild(innerEntity); -// } -// } -// } -// ) -// ); -// Map finalNewTestsResults = testsResults; -// testsResults.forEach((id, entity) -> { -// if (entity.identifier.getParentId().isPresent()) { -// finalNewTestsResults.remove(id); -// } -// }); return testTreeEntities; } 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 index 9a2605e13..79296223a 100644 --- 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 @@ -26,20 +26,18 @@ import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoa import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorClientApi; import eu.melodic.upperware.guibackend.communication.cloudiator.QueueInspector; -import eu.melodic.upperware.guibackend.controller.deployment.common.SecureVariable; +import eu.melodic.upperware.guibackend.model.provider.CloudDefinition; +import eu.melodic.upperware.guibackend.model.provider.Credential; +import eu.melodic.upperware.guibackend.service.provider.ProviderIdCreatorService; +import eu.melodic.upperware.guibackend.service.provider.ProviderService; +import eu.melodic.upperware.guibackend.service.provider.ProviderValidationService; import eu.melodic.upperware.guibackend.service.secure.store.SecureStoreService; +import eu.melodic.upperware.guibackend.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.CloudApi; -import io.github.cloudiator.rest.api.JobApi; -import io.github.cloudiator.rest.api.MatchmakingApi; -import io.github.cloudiator.rest.api.MonitoringApi; -import io.github.cloudiator.rest.api.NodeApi; -import io.github.cloudiator.rest.api.ProcessApi; -import io.github.cloudiator.rest.api.QueueApi; -import io.github.cloudiator.rest.api.SecurityApi; +import io.github.cloudiator.rest.api.*; import io.github.cloudiator.rest.model.*; @@ -53,12 +51,12 @@ import static org.junit.Assert.fail; public class ServerlessFunctionTestFactory { - private static final String CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") + private static final String CLOUDIATOR_CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") + "/eu.melodic.cloudiator-client.properties"; private static CloudiatorApi cloudiatorApi; private static MatchmakingApi matchmakingApi; - private static SecureStoreService secureStoreService; + private static ProviderService providerService; private TestInfo testInfo; private TestReporter testReporter; @@ -68,7 +66,7 @@ public class ServerlessFunctionTestFactory { static void setUp() throws IOException { Properties properties = new Properties(); - properties.load(new FileInputStream(CONFIG_FILE_PATH)); + properties.load(new FileInputStream(CLOUDIATOR_CONFIG_FILE_PATH)); CloudiatorProperties.Cloudiator cloudiator = new CloudiatorProperties.Cloudiator(); cloudiator.setUrl(properties.getProperty("cloudiator.url")); @@ -99,7 +97,13 @@ public class ServerlessFunctionTestFactory { ); matchmakingApi = new MatchmakingApi(apiClient); - secureStoreService = new SecureStoreService(cloudiatorApi); + providerService = new ProviderService( + new ProviderIdCreatorService(), + new ProviderValidationService(), + cloudiatorApi, + new SecureStoreService(cloudiatorApi), + new YamlDataService() + ); } @BeforeEach @@ -154,17 +158,49 @@ public class ServerlessFunctionTestFactory { )); } - private Map getNodeCandidates() { + private Map getFaasNodeCandidates() throws ApiException { + List faasNodes = cloudiatorApi.getFaasFromNodeList(); + + List faasNodeCandidateIds = faasNodes + .stream() + .map(Node::getNodeCandidate) + .collect(Collectors.toList()); + + Map nodeCandidates = matchmakingApi + .findNodeCandidates( + Collections.singletonList( + new OclRequirement() + .constraint( + String.format( + "nodes->forAll(n | Set{'%s'}->includes(n.id))", + String.join("','", faasNodeCandidateIds) + ) + ) + .type("OclRequirement") + ) + ) + .stream() + .collect(Collectors.toMap(NodeCandidate::getId, nodeCandidate -> nodeCandidate)); + + return faasNodes + .stream() + .collect(Collectors.toMap( + Node::getOriginId, + node -> nodeCandidates.get(node.getNodeCandidate()) + )); + } + + private Map> getNodeCandidates() { List faasNodes = cloudiatorApi.getFaasFromNodeList(); return faasNodes.stream() .collect(Collectors.toMap( Node::getOriginId, node -> { try { - return matchmakingApi.getNodeCandidate(node.getNodeCandidate()); + return Optional.of(matchmakingApi.getNodeCandidate(node.getNodeCandidate())); } catch (ApiException e) { e.printStackTrace(); - return null; + return Optional.empty(); } } )); @@ -191,15 +227,26 @@ public class ServerlessFunctionTestFactory { Map functions; Map functionNames; Map nodeCandidates; + Map userSecrets; try { functions = getFunctionsMap(); functionNames = getFunctionNames(); - nodeCandidates = getNodeCandidates(); + nodeCandidates = getFaasNodeCandidates(); + userSecrets = providerService + .getCloudDefinitionsForAllProviders() + .stream() + .map(CloudDefinition::getCredential) + .collect(Collectors.toMap(Credential::getUser, Credential::getSecret)); + } catch (Exception e) { testReporter.publishEntry( "An error occurred while fetching data from Cloudiator: " + e.getMessage() ); + dynamicNodes.add(DynamicTest.dynamicTest( + "Failed to run tests", + () -> fail("Failed to fetch data from Cloudiator") + )); return dynamicNodes; } @@ -220,19 +267,11 @@ public class ServerlessFunctionTestFactory { } else { NodeCandidate nodeCandidate = nodeCandidates.get(function.getId()); - String user = nodeCandidate.getCloud() + String user = Objects.requireNonNull(nodeCandidate.getCloud()) .getCredential() .getUser(); -// String secretLabel = secureStoreService.createKeyLabelForSecret( -// nodeCandidate.getCloud().getApi().getProviderName(), -// user -// ).getRight(); - String secretLabel = secureStoreService.createKeyLabelForSecret( - nodeCandidate.getCloud().getApi().getProviderName(), - "client" - ).getRight(); - - String secret = secureStoreService.getValueForSecureVariableLabel(secretLabel); + String secret = userSecrets.get(user); + String providerName = nodeCandidate.getCloud().getApi().getProviderName(); if (AWSLambdaService.provider.value.equals(providerName)) { @@ -246,7 +285,7 @@ public class ServerlessFunctionTestFactory { AWSLambda awsLambdaClient = buildAWSLambdaClient( user, secret, - nodeCandidate.getLocation().getName() + Objects.requireNonNull(nodeCandidate.getLocation()).getName() ); functionTestConfiguration.getTestCases().forEach(testCase -> { @@ -272,6 +311,10 @@ public class ServerlessFunctionTestFactory { testReporter.publishEntry( "An error occurred while building Azure Client: " + e.getMessage() ); + functionNode.add(DynamicTest.dynamicTest( + "Failed to build Azure Client", + () -> fail("Failed to build Azure Client") + )); continue; } @@ -294,7 +337,12 @@ public class ServerlessFunctionTestFactory { }); } } - dynamicNodes.add(DynamicContainer.dynamicContainer(functionTestConfiguration.getFunctionName(), functionNode)); + dynamicNodes.add( + DynamicContainer.dynamicContainer( + functionTestConfiguration.getFunctionName(), + functionNode + ) + ); } return dynamicNodes; } @@ -344,5 +392,6 @@ public class ServerlessFunctionTestFactory { } @Ignore("Ignored test case") - public void ignoredTestCase() {} + public void ignoredTestCase() { + } } -- GitLab From 0d3a16e667c91edd0e10ea2927cee14e70a75b23 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Tue, 19 May 2020 18:14:25 +0200 Subject: [PATCH 05/18] functionizer tt handling errors and refactoring --- .../test/ServerlessFunctionTestFactory.java | 348 +++++++++++------- 1 file changed, 221 insertions(+), 127 deletions(-) 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 index 79296223a..71430b6c6 100644 --- 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 @@ -6,6 +6,7 @@ import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -20,7 +21,6 @@ import com.microsoft.azure.management.Azure; import eu.functionizer.functionizertestingtool.model.test.FunctionTestConfiguration; import eu.functionizer.functionizertestingtool.model.test.TestCase; -import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoader; import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; @@ -28,6 +28,7 @@ import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorClient import eu.melodic.upperware.guibackend.communication.cloudiator.QueueInspector; import eu.melodic.upperware.guibackend.model.provider.CloudDefinition; import eu.melodic.upperware.guibackend.model.provider.Credential; +import eu.melodic.upperware.guibackend.model.provider.Provider; import eu.melodic.upperware.guibackend.service.provider.ProviderIdCreatorService; import eu.melodic.upperware.guibackend.service.provider.ProviderService; import eu.melodic.upperware.guibackend.service.provider.ProviderValidationService; @@ -54,9 +55,35 @@ public class ServerlessFunctionTestFactory { private static final String CLOUDIATOR_CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") + "/eu.melodic.cloudiator-client.properties"; + private enum Stage { + START("TEST FACTORY INITIALIZATION"), + LOAD_CONFIG("LOAD TEST CONFIG FILE"), + FETCH_CLOUDIATOR("FETCH CLOUDIATOR DATA"), + GATHER_FUNCTION_DATA("GATHER FUNCTION CLOUD DATA"), + BUILD_AWS_CLIENT("BUILD AWS LAMBDA CLIENT"), + BUILD_AZURE_CLIENT("BUILD AZURE 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 String name; + + Stage(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + private static Map providerMap; + private static CloudiatorApi cloudiatorApi; private static MatchmakingApi matchmakingApi; private static ProviderService providerService; + private static Stage stage = null; private TestInfo testInfo; private TestReporter testReporter; @@ -104,6 +131,11 @@ public class ServerlessFunctionTestFactory { new SecureStoreService(cloudiatorApi), new YamlDataService() ); + + providerMap = new HashMap<>(); + for (Provider provider : Provider.values()) { + providerMap.put(provider.value, provider); + } } @BeforeEach @@ -118,10 +150,15 @@ public class ServerlessFunctionTestFactory { } private static String getTaskInterfaceFunctionName(Task task) { - FaasInterface taskInterface = (FaasInterface) Objects + Optional taskInterface = Objects .requireNonNull(task.getInterfaces()) - .get(0); - return taskInterface.getFunctionName(); + .stream() + .findFirst(); + if (taskInterface.isPresent()) { + FaasInterface faasInterface = (FaasInterface) taskInterface.get(); + return faasInterface.getFunctionName(); + } + return null; } private Map getFunctionsMap() { @@ -135,50 +172,53 @@ public class ServerlessFunctionTestFactory { )); } - private Map getCloudsMap() { - List clouds = cloudiatorApi.getCloudList(); - return clouds.stream().collect(Collectors.toMap(Cloud::getId, cloud -> cloud)); - } - - private Map getFunctionNames() { + private Stream getFaasTasks() { List jobs = cloudiatorApi.getJobList(); return jobs .stream() .map(Job::getTasks) .filter(Objects::nonNull) .flatMap(List::stream) - .filter( - task -> FaasInterface.class.equals( - Objects.requireNonNull(task.getInterfaces()).get(0).getClass() - ) - ) + .filter(task -> Objects.nonNull(task) + && Objects.nonNull(task.getInterfaces()) + && Objects.requireNonNull(task.getInterfaces()) + .stream() + .findFirst() + .filter(anInterface -> FaasInterface.class.equals(anInterface.getClass())) + .isPresent() + ); + } + + private Map getFunctionNames() { + Stream faasTasks = getFaasTasks(); + return faasTasks .collect(Collectors.toMap( Task::getName, ServerlessFunctionTestFactory::getTaskInterfaceFunctionName )); } + private List getNodeCandidatesByIds(List ids) throws ApiException { + return matchmakingApi.findNodeCandidates( + Collections.singletonList(new OclRequirement() + .constraint(String.format( + "nodes->forAll(n | Set{'%s'}->includes(n.id))", + String.join("','", ids) + )) + .type("OclRequirement") + ) + ); + } + private Map getFaasNodeCandidates() throws ApiException { List faasNodes = cloudiatorApi.getFaasFromNodeList(); - List faasNodeCandidateIds = faasNodes + List nodeCandidateIds = faasNodes .stream() .map(Node::getNodeCandidate) .collect(Collectors.toList()); - Map nodeCandidates = matchmakingApi - .findNodeCandidates( - Collections.singletonList( - new OclRequirement() - .constraint( - String.format( - "nodes->forAll(n | Set{'%s'}->includes(n.id))", - String.join("','", faasNodeCandidateIds) - ) - ) - .type("OclRequirement") - ) - ) + Map nodeCandidates = getNodeCandidatesByIds(nodeCandidateIds) .stream() .collect(Collectors.toMap(NodeCandidate::getId, nodeCandidate -> nodeCandidate)); @@ -190,29 +230,149 @@ public class ServerlessFunctionTestFactory { )); } - private Map> getNodeCandidates() { - List faasNodes = cloudiatorApi.getFaasFromNodeList(); - return faasNodes.stream() - .collect(Collectors.toMap( - Node::getOriginId, - node -> { - try { - return Optional.of(matchmakingApi.getNodeCandidate(node.getNodeCandidate())); - } catch (ApiException e) { - e.printStackTrace(); - return Optional.empty(); - } + private void failDynamicNode( + List dynamicNode, Stage stage, String cause + ) { + dynamicNode.add(DynamicTest.dynamicTest( + "Dynamic node construction failed at stage " + stage.getName(), + () -> fail(cause) + )); + } + + private List prepareFunctionTestCases( + FunctionTestConfiguration functionTestConfiguration, + Map nodeCandidates, + Map userSecrets, + String functionName, + Function function + ) { + List functionNode = new ArrayList<>(); + String canonicalFunctionName = functionTestConfiguration.getFunctionName(); + String functionIdentifier = canonicalFunctionName.toLowerCase(); + + if (function == null) { + testReporter.publishEntry( + "Function: " + functionIdentifier + " was not found among deployed functions. Omitting" + ); + functionTestConfiguration.getTestCases().forEach(testCase -> { + functionNode.add(DynamicTest.dynamicTest( + createTestCaseDisplayName( + testCase.getEvent(), + testCase.getExpectedOutput() + ), + this::ignoredTestCase + )); + }); + return functionNode; + } + + NodeCandidate nodeCandidate = nodeCandidates.get(function.getId()); + Cloud cloud = nodeCandidate.getCloud(); + if (cloud == null) { + failDynamicNode( + functionNode, + Stage.GATHER_FUNCTION_DATA, + "Cloud not found" + ); + return functionNode; + } + String user = Objects.requireNonNull(nodeCandidate.getCloud()) + .getCredential() + .getUser(); + String secret = userSecrets.get(user); + Provider provider = providerMap.get( + nodeCandidate.getCloud().getApi().getProviderName() + ); + + switch (provider) { + case AWS_EC2: + String awsFunctionName = String.join( + "-", + function.getStackId(), + functionName + ); + + AWSLambda awsLambdaClient = buildAWSLambdaClient( + user, + secret, + Objects.requireNonNull(nodeCandidate.getLocation()).getName() + ); + + functionTestConfiguration.getTestCases().forEach(testCase -> { + testCase.setFunctionName(awsFunctionName); + + functionNode.add(DynamicTest.dynamicTest( + createTestCaseDisplayName( + testCase.getEvent(), + testCase.getExpectedOutput() + ), + () -> executeAWSLambdaTestCase(awsLambdaClient, testCase) + )); + }); + break; + + case AZURE: + Azure azureClient; + String functionKey; + + try { + stage = Stage.BUILD_AZURE_CLIENT; + azureClient = AzureFunctionsService.buildAzureClient(user, secret); + + stage = Stage.GET_AZURE_FUNCTION_KEY; + functionKey = AzureFunctionsService.getFunctionKey(azureClient, function); + } catch (Exception e) { + testReporter.publishEntry( + "An error occurred while building Azure Client: " + e.getMessage() + ); + failDynamicNode(functionNode, Stage.BUILD_AZURE_CLIENT, e.getMessage()); + break; } - )); + + String azureFunctionEndpoint = AzureFunctionsService.buildFunctionUrl( + function.getStackId(), + functionTestConfiguration.getTriggerPath() + ); + + functionTestConfiguration.getTestCases().forEach(testCase -> { + testCase.setFunctionName(functionName); + + functionNode.add(DynamicTest.dynamicTest( + createTestCaseDisplayName( + testCase.getEvent(), + testCase.getExpectedOutput() + ), + () -> executeAzureTestCase(azureFunctionEndpoint, functionKey, testCase) + )); + }); + break; + + default: + testReporter.publishEntry( + "Function was deployed on unsupported Provider Cloud. Omitting." + ); + functionTestConfiguration.getTestCases().forEach(testCase -> { + functionNode.add(DynamicTest.dynamicTest( + createTestCaseDisplayName( + testCase.getEvent(), + testCase.getExpectedOutput() + ), + this::ignoredTestCase + )); + }); + } + return functionNode; } @TestFactory public Collection dynamicTestNodes() { + stage = Stage.START; testReporter.publishEntry("Collecting test cases..."); List dynamicNodes = new ArrayList<>(); List functionTests; + stage = Stage.LOAD_CONFIG; try { functionTests = TestConfigurationLoader .loadTestConfiguration() @@ -221,6 +381,7 @@ public class ServerlessFunctionTestFactory { testReporter.publishEntry( "An error occurred while loading test configuration: " + e.getMessage() ); + failDynamicNode(dynamicNodes, stage, e.getMessage()); return dynamicNodes; } @@ -229,6 +390,7 @@ public class ServerlessFunctionTestFactory { Map nodeCandidates; Map userSecrets; + stage = Stage.FETCH_CLOUDIATOR; try { functions = getFunctionsMap(); functionNames = getFunctionNames(); @@ -243,100 +405,26 @@ public class ServerlessFunctionTestFactory { testReporter.publishEntry( "An error occurred while fetching data from Cloudiator: " + e.getMessage() ); - dynamicNodes.add(DynamicTest.dynamicTest( - "Failed to run tests", - () -> fail("Failed to fetch data from Cloudiator") - )); + failDynamicNode(dynamicNodes, Stage.FETCH_CLOUDIATOR, e.getMessage()); return dynamicNodes; } for (FunctionTestConfiguration functionTestConfiguration : functionTests) { + stage = Stage.GATHER_FUNCTION_DATA; + String canonicalFunctionName = functionTestConfiguration.getFunctionName(); String functionIdentifier = canonicalFunctionName.toLowerCase(); String functionName = functionNames.get(canonicalFunctionName); - List functionNode = new ArrayList<>(); - Function function = functions.get(functionIdentifier); + List functionNode = prepareFunctionTestCases( + functionTestConfiguration, + nodeCandidates, + userSecrets, + functionName, + function + ); - if (function == null) { - testReporter.publishEntry( - "Function: " + functionIdentifier + " not found among deployed functions. Omitting" - ); - functionNode.add(DynamicTest.dynamicTest("Ignored", this::ignoredTestCase)); - } else { - - NodeCandidate nodeCandidate = nodeCandidates.get(function.getId()); - String user = Objects.requireNonNull(nodeCandidate.getCloud()) - .getCredential() - .getUser(); - String secret = userSecrets.get(user); - - String providerName = nodeCandidate.getCloud().getApi().getProviderName(); - - if (AWSLambdaService.provider.value.equals(providerName)) { - - String awsFunctionName = String.join( - "-", - function.getStackId(), - functionName - ); - - AWSLambda awsLambdaClient = buildAWSLambdaClient( - user, - secret, - Objects.requireNonNull(nodeCandidate.getLocation()).getName() - ); - - functionTestConfiguration.getTestCases().forEach(testCase -> { - testCase.setFunctionName(awsFunctionName); - - functionNode.add(DynamicTest.dynamicTest( - createTestCaseDisplayName( - functionName, - testCase.getEvent(), - testCase.getExpectedOutput() - ), - () -> executeAWSLambdaTestCase(awsLambdaClient, testCase) - )); - }); - - } else if (AzureFunctionsService.provider.value.equals(providerName)) { - Azure azureClient; - String functionKey; - try { - azureClient = AzureFunctionsService.buildAzureClient(user, secret); - functionKey = AzureFunctionsService.getFunctionKey(azureClient, function); - } catch (Exception e) { - testReporter.publishEntry( - "An error occurred while building Azure Client: " + e.getMessage() - ); - functionNode.add(DynamicTest.dynamicTest( - "Failed to build Azure Client", - () -> fail("Failed to build Azure Client") - )); - continue; - } - - String azureFunctionEndpoint = AzureFunctionsService.buildFunctionUrl( - function.getStackId(), - functionTestConfiguration.getTriggerPath() - ); - - functionTestConfiguration.getTestCases().forEach(testCase -> { - testCase.setFunctionName(functionName); - - functionNode.add(DynamicTest.dynamicTest( - createTestCaseDisplayName( - functionName, - testCase.getEvent(), - testCase.getExpectedOutput() - ), - () -> executeAzureTestCase(azureFunctionEndpoint, functionKey, testCase) - )); - }); - } - } dynamicNodes.add( DynamicContainer.dynamicContainer( functionTestConfiguration.getFunctionName(), @@ -344,11 +432,12 @@ public class ServerlessFunctionTestFactory { ) ); } + stage = Stage.END; return dynamicNodes; } - public static String createTestCaseDisplayName(String functionName, String input, String expectedOutput) { + public static String createTestCaseDisplayName(String input, String expectedOutput) { return "EVENT=" + input + ", EXPECTED OUTPUT=" + expectedOutput; } @@ -362,6 +451,8 @@ public class ServerlessFunctionTestFactory { } public void executeAWSLambdaTestCase(AWSLambda awsLambda, TestCase testCase) { + stage = Stage.TEST_EXECUTION; + testReporter.publishEntry("bleble"); InvokeRequest invokeRequest = new InvokeRequest() .withFunctionName(testCase.getFunctionName()) @@ -370,6 +461,7 @@ public class ServerlessFunctionTestFactory { String resultString = null; try { + stage = Stage.AWS_INVOKE; InvokeResult result = awsLambda.invoke(invokeRequest); resultString = new String(result.getPayload().array(), StandardCharsets.UTF_8); } catch (ServiceException e) { @@ -383,6 +475,7 @@ public class ServerlessFunctionTestFactory { String functionKey, TestCase testCase ) throws Exception { + stage = Stage.TEST_EXECUTION; String response = AzureFunctionsService.invokeFunction( endpoint, functionKey, @@ -393,5 +486,6 @@ public class ServerlessFunctionTestFactory { @Ignore("Ignored test case") public void ignoredTestCase() { + fail("Ignored"); } } -- GitLab From 7a02380090f4742acd28c1bf4a318e814705c345 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Tue, 26 May 2020 14:50:40 +0200 Subject: [PATCH 06/18] functionizer tt refactor --- functionizer-testing-tool/pom.xml | 5 + .../FunctionizerTestingToolController.java | 2 + .../TestingToolContext.java | 88 ---- .../{security => }/WebSecurity.java | 14 +- .../model/CloudiatorData.java | 20 + .../{test => }/FunctionTestConfiguration.java | 2 +- .../model/{test => }/TestCase.java | 2 +- .../model/{test => }/TestConfiguration.java | 2 +- .../service/TestRunner.java | 30 +- .../service/provider/AWSLambdaService.java | 36 +- .../provider/AzureFunctionsService.java | 11 +- .../service/test/FunctionizerReportData.java | 33 +- ...ner.java => FunctionizerTestListener.java} | 6 +- .../test/ServerlessFunctionTestFactory.java | 435 ++++++++++-------- .../service/yaml/TestConfigurationLoader.java | 2 +- .../src/main/resources/logback.xml | 11 + 16 files changed, 342 insertions(+), 357 deletions(-) rename functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/{security => }/WebSecurity.java (80%) create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/CloudiatorData.java rename functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/{test => }/FunctionTestConfiguration.java (76%) rename functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/{test => }/TestCase.java (74%) rename functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/{test => }/TestConfiguration.java (69%) rename functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/{FunctionizerTestReportGeneratingListener.java => FunctionizerTestListener.java} (95%) create mode 100644 functionizer-testing-tool/src/main/resources/logback.xml diff --git a/functionizer-testing-tool/pom.xml b/functionizer-testing-tool/pom.xml index 94707f993..c53d4340c 100644 --- a/functionizer-testing-tool/pom.xml +++ b/functionizer-testing-tool/pom.xml @@ -75,6 +75,11 @@ compile + + org.ow2.paasage + jwt-commons + + com.amazonaws aws-java-sdk-core 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 index 888093d7d..ab096c42d 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java @@ -3,6 +3,7 @@ package eu.functionizer.functionizertestingtool; import eu.functionizer.functionizertestingtool.service.TestRunner; import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; 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.*; @@ -11,6 +12,7 @@ import java.util.Collection; import java.util.Map; @RestController +@Slf4j @AllArgsConstructor(onConstructor = @__(@Autowired)) public class FunctionizerTestingToolController { 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 index 931a3ba85..66bc8d6af 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/TestingToolContext.java @@ -1,104 +1,16 @@ package eu.functionizer.functionizertestingtool; -import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; -import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorClientApi; -import eu.melodic.upperware.guibackend.communication.cloudiator.QueueInspector; 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.CloudiatorProperties; -import io.github.cloudiator.rest.ApiClient; -import io.github.cloudiator.rest.api.*; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; @Configuration public class TestingToolContext { -// @Bean -// public RestTemplate getRestTemplate() { -// -// SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); -// simpleClientHttpRequestFactory.setConnectTimeout(180000); -// simpleClientHttpRequestFactory.setReadTimeout(120000); -// -// return new RestTemplate(simpleClientHttpRequestFactory); -// } -// -// @Bean -// public ApiClient apiClient(CloudiatorProperties cloudiatorProperties) { -// ApiClient apiClient = new ApiClient(); -// apiClient.setBasePath(cloudiatorProperties.getCloudiator().getUrl()); -// apiClient.setApiKey(cloudiatorProperties.getCloudiator().getApiKey()); -// apiClient.setReadTimeout(cloudiatorProperties.getCloudiator().getHttpReadTimeout()); -// return apiClient; -// } -// -// @Bean -// public CloudApi cloudApi(ApiClient apiClient) { -// return new CloudApi(apiClient); -// } -// -// @Bean -// public SecurityApi securityApi(ApiClient apiClient) { -// return new SecurityApi(apiClient); -// } -// -// @Bean -// public NodeApi nodeApi(ApiClient apiClient) { -// return new NodeApi(apiClient); -// } -// -// @Bean -// public QueueApi queueApi(ApiClient apiClient) { -// return new QueueApi(apiClient); -// } -// -// @Bean -// public ProcessApi processApi(ApiClient apiClient) { -// return new ProcessApi((apiClient)); -// } -// -// @Bean -// public JobApi jobApi(ApiClient apiClient) { -// return new JobApi(apiClient); -// } -// -// @Bean -// public MonitoringApi monitoringApi(ApiClient apiClient) { -// return new MonitoringApi(apiClient); -// } -// @Bean public JWTService jWTService(MelodicSecurityProperties melodicSecurityProperties) { return new JWTServiceImpl(melodicSecurityProperties); } -// -// @Bean -// public MatchmakingApi matchmakingApi(ApiClient apiClient) { -// return new MatchmakingApi(apiClient); -// } -// -// @Bean -// QueueInspector queueInspector(QueueApi queueApi, CloudiatorProperties cloudiatorProperties) { -// return new QueueInspector(queueApi, cloudiatorProperties); -// } - -// @Bean -// 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 -// ); -// } } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/WebSecurity.java similarity index 80% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java rename to functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/WebSecurity.java index b86ffa7a8..0ba220231 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/security/WebSecurity.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/WebSecurity.java @@ -1,4 +1,4 @@ -package eu.functionizer.functionizertestingtool.security; +package eu.functionizer.functionizertestingtool; import eu.paasage.upperware.security.authapi.JWTAuthorizationFilter; import eu.paasage.upperware.security.authapi.token.JWTService; @@ -29,16 +29,12 @@ public class WebSecurity extends WebSecurityConfigurerAdapter { if (securityEnabled) { log.info("Running WITH security"); - http.csrf().disable().antMatcher("/auth/**") - .authorizeRequests() + http.cors().and().csrf().disable().authorizeRequests() .anyRequest().authenticated() .and() - .addFilterBefore( - new JWTAuthorizationFilter(authenticationManager(), jwtService), - BasicAuthenticationFilter.class - ); - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); - + .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() 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 000000000..44990d100 --- /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/test/FunctionTestConfiguration.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestConfiguration.java similarity index 76% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java rename to functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestConfiguration.java index a19469d31..ca5aa28e8 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/FunctionTestConfiguration.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestConfiguration.java @@ -1,4 +1,4 @@ -package eu.functionizer.functionizertestingtool.model.test; +package eu.functionizer.functionizertestingtool.model; import lombok.Data; diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCase.java similarity index 74% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java rename to functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCase.java index 7933a0c2d..7bce6ba8d 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestCase.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCase.java @@ -1,4 +1,4 @@ -package eu.functionizer.functionizertestingtool.model.test; +package eu.functionizer.functionizertestingtool.model; import lombok.Data; diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestConfiguration.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java similarity index 69% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestConfiguration.java rename to functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java index 895d1b3f4..6dd324ab9 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/test/TestConfiguration.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java @@ -1,4 +1,4 @@ -package eu.functionizer.functionizertestingtool.model.test; +package eu.functionizer.functionizertestingtool.model; import lombok.Data; 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 index 87f9dd611..9ba8b1e62 100644 --- 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 @@ -1,12 +1,9 @@ package eu.functionizer.functionizertestingtool.service; -import java.io.PrintWriter; import java.util.Collection; -import java.util.Map; -import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; -import eu.functionizer.functionizertestingtool.service.test.FunctionizerTestReportGeneratingListener; +import eu.functionizer.functionizertestingtool.service.test.FunctionizerTestListener; import eu.functionizer.functionizertestingtool.service.test.ServerlessFunctionTestFactory; import lombok.AllArgsConstructor; @@ -17,9 +14,6 @@ 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.junit.platform.launcher.listeners.SummaryGeneratingListener; -import org.junit.platform.launcher.listeners.TestExecutionSummary; -import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -32,25 +26,29 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass @AllArgsConstructor(onConstructor = @__(@Autowired)) public class TestRunner { - public AzureFunctionsService azureFunctionsService; - public Collection 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); - SummaryGeneratingListener listener = new SummaryGeneratingListener(); - FunctionizerTestReportGeneratingListener myListener = - new FunctionizerTestReportGeneratingListener(); - launcher.registerTestExecutionListeners(listener, myListener); - launcher.execute(request); + log.debug("Initiating Listener"); + + FunctionizerTestListener listener = + new FunctionizerTestListener(); - TestExecutionSummary summary = listener.getSummary(); - FunctionizerReportData report = myListener.getReport(); + log.debug("Registering Listener"); + launcher.registerTestExecutionListeners(listener); + launcher.execute(request); + log.debug("Generating Report"); + FunctionizerReportData report = listener.getReport(); return report.getTestTree(); } 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 index 24e1c7998..c17ddb2b6 100644 --- 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 @@ -5,43 +5,15 @@ 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 com.amazonaws.services.lambda.model.ServiceException; -import eu.melodic.upperware.guibackend.model.provider.Provider; -import io.github.cloudiator.rest.model.CloudCredential; -import org.springframework.stereotype.Service; -import java.nio.charset.StandardCharsets; - -@Service public class AWSLambdaService { - public final static Provider provider = Provider.AWS_EC2; - - private static AWSLambdaClientBuilder builder = AWSLambdaClientBuilder.standard(); - private static AWSLambda awsLambda; - public void buildClient(CloudCredential cloudCredential, String region) { - BasicAWSCredentials credentials = new BasicAWSCredentials( - cloudCredential.getUser(), cloudCredential.getSecret() - ); + public static AWSLambda buildClient(String user, String secret, String location) { + BasicAWSCredentials credentials = new BasicAWSCredentials(user, secret); - awsLambda = builder + return AWSLambdaClientBuilder.standard() .withCredentials(new AWSStaticCredentialsProvider(credentials)) - .withRegion(Regions.fromName(region)) + .withRegion(Regions.fromName(location)) .build(); } - - public static String invokeFunction(String functionName, String payload) { - InvokeRequest invokeRequest = new InvokeRequest() - .withFunctionName(functionName) - .withPayload(payload); - - try { - InvokeResult result = awsLambda.invoke(invokeRequest); - return new String(result.getPayload().array(), StandardCharsets.UTF_8); - } catch (ServiceException e) { - return null; - } - } } 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 index f1728f450..ca208ecd7 100644 --- 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 @@ -4,20 +4,19 @@ 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.melodic.upperware.guibackend.model.provider.Provider; -import io.github.cloudiator.rest.model.CloudCredential; import io.github.cloudiator.rest.model.Function; -import org.springframework.http.*; -import org.springframework.stereotype.Service; +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; -@Service public class AzureFunctionsService { - public final static Provider provider = Provider.AZURE; private final static String ENDPOINT_STATIC = "azurewebsites.net/api"; private final static String CLIENT_TENANT_DELIMITER = ":"; 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 index 1b81cb433..4f3df4459 100644 --- 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 @@ -1,6 +1,7 @@ package eu.functionizer.functionizertestingtool.service.test; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import org.junit.ComparisonFailure; import org.junit.Test; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestExecutionResult; @@ -112,10 +113,22 @@ public class FunctionizerReportData { public static class Failure { String displayName; +// String event; +// String expectedOutput; +// String actualOutput; String cause; - public Failure(String displayName, String cause) { + public Failure( + String displayName, +// String event, +// String expectedOutput, +// String actualOutput, + String cause + ) { this.displayName = displayName; +// this.event = event; +// this.expectedOutput = expectedOutput; +// this.actualOutput = actualOutput; this.cause = cause; } } @@ -189,14 +202,30 @@ public class FunctionizerReportData { public class TestTreeEntity { private String displayName; private String status; + private String event; + private String expectedOutput = ""; + private String actualOutput = ""; private String message = ""; private double duration; private List children; public TestTreeEntity(TestIdentifier identifier, TestExecutionResult result) { this.displayName = identifier.getDisplayName(); + String[] eventExpectedOutput = this.displayName.split(";"); + if (eventExpectedOutput.length == 2) { + this.event = eventExpectedOutput[0]; + this.expectedOutput = eventExpectedOutput[1]; + } + this.status = result.getStatus().toString(); - result.getThrowable().ifPresent(value -> this.message = value.getMessage()); + result.getThrowable().ifPresent(error -> { + this.message = error.getMessage(); + if (error instanceof ComparisonFailure) { + ComparisonFailure comparisonFailure = (ComparisonFailure) error; + this.expectedOutput = comparisonFailure.getExpected(); + this.actualOutput = comparisonFailure.getActual(); + } + }); this.duration = getDurationInSeconds(identifier); this.children = new ArrayList<>(); } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestReportGeneratingListener.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestListener.java similarity index 95% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestReportGeneratingListener.java rename to functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestListener.java index cf7e823b4..3b0870a98 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestReportGeneratingListener.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestListener.java @@ -12,7 +12,7 @@ import java.util.stream.Stream; import static java.util.stream.Stream.concat; -public class FunctionizerTestReportGeneratingListener implements TestExecutionListener { +public class FunctionizerTestListener implements TestExecutionListener { private final Clock clock; @@ -20,11 +20,11 @@ public class FunctionizerTestReportGeneratingListener implements TestExecutionLi private FunctionizerReportData reportData; - public FunctionizerTestReportGeneratingListener() { + public FunctionizerTestListener() { this(Clock.systemDefaultZone()); } - private FunctionizerTestReportGeneratingListener(Clock clock) { + private FunctionizerTestListener(Clock clock) { this.clock = clock; } 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 index 71430b6c6..b9e365f01 100644 --- 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 @@ -8,19 +8,17 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -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 com.amazonaws.services.lambda.model.ServiceException; import com.microsoft.azure.management.Azure; -import eu.functionizer.functionizertestingtool.model.test.FunctionTestConfiguration; -import eu.functionizer.functionizertestingtool.model.test.TestCase; +import eu.functionizer.functionizertestingtool.model.CloudiatorData; +import eu.functionizer.functionizertestingtool.model.FunctionTestConfiguration; +import eu.functionizer.functionizertestingtool.model.TestCase; +import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoader; import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; @@ -42,6 +40,7 @@ import io.github.cloudiator.rest.api.*; import io.github.cloudiator.rest.model.*; +import lombok.extern.slf4j.Slf4j; import org.junit.Ignore; import org.junit.jupiter.api.*; import org.springframework.web.server.ResponseStatusException; @@ -49,7 +48,7 @@ import org.springframework.web.server.ResponseStatusException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; - +@Slf4j public class ServerlessFunctionTestFactory { private static final String CLOUDIATOR_CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") @@ -59,6 +58,7 @@ public class ServerlessFunctionTestFactory { 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_AWS_CLIENT("BUILD AWS LAMBDA CLIENT"), BUILD_AZURE_CLIENT("BUILD AZURE CLIENT"), @@ -85,10 +85,6 @@ public class ServerlessFunctionTestFactory { private static ProviderService providerService; private static Stage stage = null; - private TestInfo testInfo; - private TestReporter testReporter; - - @BeforeAll static void setUp() throws IOException { @@ -138,27 +134,122 @@ public class ServerlessFunctionTestFactory { } } - @BeforeEach - void initTestCase(TestInfo testInfo, TestReporter testReporter) { - this.testInfo = testInfo; - this.testReporter = testReporter; + private void failDynamicNode( + List dynamicNode, Stage stage, String cause + ) { + log.info("Dynamic Node construction failed at the stage " + stage.getName()); + + dynamicNode.add(DynamicTest.dynamicTest( + "Dynamic node construction failed at the stage " + stage.getName(), + () -> fail(cause) + )); } + @TestFactory + public Collection dynamicTestNodes() { + log.info("Test Factory started"); + stage = Stage.START; - private static String extractTaskName(Function function) { - return Objects.requireNonNull(function.getStackId()).split("-")[0]; - } + List dynamicNodes = new ArrayList<>(); + List functionTests; - 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(); + log.info("Loading test configuration"); + stage = Stage.LOAD_CONFIG; + try { + functionTests = TestConfigurationLoader + .loadTestConfiguration() + .getTests(); + } catch (ResponseStatusException e) { + failDynamicNode(dynamicNodes, stage, e.getMessage()); + return dynamicNodes; + } + + CloudiatorData cloudiatorData; + + log.info("Fetching data from Cloudiator"); + stage = Stage.FETCH_CLOUDIATOR; + try { + cloudiatorData = fetchCloudiatorData(); + } catch (Exception e) { + failDynamicNode(dynamicNodes, stage, e.getMessage()); + 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.info("Looking up Function in the deployed Function list"); + Function function = cloudiatorData + .getDeployedFunctions() + .get(functionConfigurationName.toLowerCase()); + + List functionNode; + + if (function == null) { + log.info( + "Function {} not found in the deployed Function list. All test cases will be ignored", + functionConfigurationName + ); + functionNode = new ArrayList<>(); + ignoreTestCases(functionNode, functionTestConfiguration.getTestCases()); + } else { + + String functionDeployedName = cloudiatorData + .getFunctionDeployNames() + .get(functionConfigurationName); + + NodeCandidate nodeCandidate = cloudiatorData + .getNodeCandidates() + .get(function.getId()); + + log.info("Collecting test cases"); + functionNode = prepareFunctionNode( + functionTestConfiguration, + nodeCandidate, + cloudiatorData.getUserSecrets(), + functionDeployedName, + function + ); + } + + dynamicNodes.add( + DynamicContainer.dynamicContainer( + functionTestConfiguration.getFunctionName(), + functionNode + ) + ); } - return null; + stage = Stage.END; + return dynamicNodes; + } + + private CloudiatorData fetchCloudiatorData( + ) throws ApiException { + + log.info("Fetching Functions from Cloudiator"); + Map functions = getFunctionsMap(); + + log.info("Fetching Function Names from Cloudiator"); + Map functionDeployNames = getFunctionDeployNames(); + + log.info("Fetching Node Candidates from Cloudiator"); + Map nodeCandidates = getFaasNodeCandidates(); + + log.info("Fetching Cloud Provider Credentials"); + stage = Stage.FETCH_CREDENTIALS; + Map userSecrets = getCredentials(); + + return new CloudiatorData( + functions, + functionDeployNames, + nodeCandidates, + userSecrets + ); } private Map getFunctionsMap() { @@ -167,13 +258,27 @@ public class ServerlessFunctionTestFactory { .stream() .filter(function -> !Objects.requireNonNull(function.getStackId()).isEmpty()) .collect(Collectors.toMap( - ServerlessFunctionTestFactory::extractTaskName, + 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) @@ -182,37 +287,30 @@ public class ServerlessFunctionTestFactory { .filter(task -> Objects.nonNull(task) && Objects.nonNull(task.getInterfaces()) && Objects.requireNonNull(task.getInterfaces()) - .stream() - .findFirst() - .filter(anInterface -> FaasInterface.class.equals(anInterface.getClass())) - .isPresent() + .stream() + .findFirst() + .filter(anInterface -> anInterface instanceof FaasInterface) + .isPresent() ); } - private Map getFunctionNames() { - Stream faasTasks = getFaasTasks(); - return faasTasks - .collect(Collectors.toMap( - Task::getName, - ServerlessFunctionTestFactory::getTaskInterfaceFunctionName - )); - } - - private List getNodeCandidatesByIds(List ids) throws ApiException { - return matchmakingApi.findNodeCandidates( - Collections.singletonList(new OclRequirement() - .constraint(String.format( - "nodes->forAll(n | Set{'%s'}->includes(n.id))", - String.join("','", ids) - )) - .type("OclRequirement") - ) - ); + 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) @@ -230,45 +328,40 @@ public class ServerlessFunctionTestFactory { )); } - private void failDynamicNode( - List dynamicNode, Stage stage, String cause - ) { - dynamicNode.add(DynamicTest.dynamicTest( - "Dynamic node construction failed at stage " + stage.getName(), - () -> fail(cause) - )); + 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 prepareFunctionTestCases( + private List prepareFunctionNode( FunctionTestConfiguration functionTestConfiguration, - Map nodeCandidates, + NodeCandidate nodeCandidate, Map userSecrets, - String functionName, + String functionDeployedName, Function function ) { List functionNode = new ArrayList<>(); - String canonicalFunctionName = functionTestConfiguration.getFunctionName(); - String functionIdentifier = canonicalFunctionName.toLowerCase(); - - if (function == null) { - testReporter.publishEntry( - "Function: " + functionIdentifier + " was not found among deployed functions. Omitting" - ); - functionTestConfiguration.getTestCases().forEach(testCase -> { - functionNode.add(DynamicTest.dynamicTest( - createTestCaseDisplayName( - testCase.getEvent(), - testCase.getExpectedOutput() - ), - this::ignoredTestCase - )); - }); - return functionNode; - } - NodeCandidate nodeCandidate = nodeCandidates.get(function.getId()); Cloud cloud = nodeCandidate.getCloud(); if (cloud == null) { + log.info("Cloud not found. Omitting test cases"); failDynamicNode( functionNode, Stage.GATHER_FUNCTION_DATA, @@ -276,6 +369,8 @@ public class ServerlessFunctionTestFactory { ); return functionNode; } + + log.info("Retrieving Cloud credentials"); String user = Objects.requireNonNull(nodeCandidate.getCloud()) .getCredential() .getUser(); @@ -286,27 +381,42 @@ public class ServerlessFunctionTestFactory { switch (provider) { case AWS_EC2: + log.debug("Building AWS Function name"); String awsFunctionName = String.join( "-", function.getStackId(), - functionName + functionDeployedName ); - AWSLambda awsLambdaClient = buildAWSLambdaClient( + log.debug("Building AWS Lambda client"); + AWSLambda awsLambdaClient = AWSLambdaService.buildClient( user, secret, - Objects.requireNonNull(nodeCandidate.getLocation()).getName() + Objects.requireNonNull(Objects.requireNonNull( + nodeCandidate + .getLocation()) + .getParent()) + .getProviderId() ); functionTestConfiguration.getTestCases().forEach(testCase -> { - testCase.setFunctionName(awsFunctionName); + log.debug( + "Creating test case: event={}, expected output={}", + testCase.getEvent(), + testCase.getExpectedOutput() + ); functionNode.add(DynamicTest.dynamicTest( createTestCaseDisplayName( testCase.getEvent(), testCase.getExpectedOutput() ), - () -> executeAWSLambdaTestCase(awsLambdaClient, testCase) + () -> executeAWSLambdaTestCase( + awsLambdaClient, + awsFunctionName, + testCase.getEvent(), + testCase.getExpectedOutput() + ) )); }); break; @@ -316,172 +426,103 @@ public class ServerlessFunctionTestFactory { String functionKey; try { + log.debug("Building Azure client"); stage = Stage.BUILD_AZURE_CLIENT; azureClient = AzureFunctionsService.buildAzureClient(user, secret); + log.debug("Fetching Function Key"); stage = Stage.GET_AZURE_FUNCTION_KEY; functionKey = AzureFunctionsService.getFunctionKey(azureClient, function); } catch (Exception e) { - testReporter.publishEntry( - "An error occurred while building Azure Client: " + e.getMessage() - ); - failDynamicNode(functionNode, Stage.BUILD_AZURE_CLIENT, e.getMessage()); + failDynamicNode(functionNode, stage, e.getMessage()); break; } + log.debug("Building Function URL"); String azureFunctionEndpoint = AzureFunctionsService.buildFunctionUrl( function.getStackId(), functionTestConfiguration.getTriggerPath() ); + log.info("Function URL = {}", azureFunctionEndpoint); functionTestConfiguration.getTestCases().forEach(testCase -> { - testCase.setFunctionName(functionName); + testCase.setFunctionName(functionDeployedName); + log.debug( + "Creating test case: event={}, expected output={}", + testCase.getEvent(), + testCase.getExpectedOutput() + ); functionNode.add(DynamicTest.dynamicTest( createTestCaseDisplayName( testCase.getEvent(), testCase.getExpectedOutput() ), - () -> executeAzureTestCase(azureFunctionEndpoint, functionKey, testCase) + () -> executeAzureTestCase( + azureFunctionEndpoint, + functionKey, + testCase.getEvent(), + testCase.getExpectedOutput() + ) )); }); break; default: - testReporter.publishEntry( - "Function was deployed on unsupported Provider Cloud. Omitting." - ); - functionTestConfiguration.getTestCases().forEach(testCase -> { - functionNode.add(DynamicTest.dynamicTest( - createTestCaseDisplayName( - testCase.getEvent(), - testCase.getExpectedOutput() - ), - this::ignoredTestCase - )); - }); + log.info("Function {} has been deployed on unsupported Cloud. Omitting", functionDeployedName); + ignoreTestCases(functionNode, functionTestConfiguration.getTestCases()); } return functionNode; } - @TestFactory - public Collection dynamicTestNodes() { - stage = Stage.START; - testReporter.publishEntry("Collecting test cases..."); - - List dynamicNodes = new ArrayList<>(); - List functionTests; - - stage = Stage.LOAD_CONFIG; - try { - functionTests = TestConfigurationLoader - .loadTestConfiguration() - .getTests(); - } catch (ResponseStatusException e) { - testReporter.publishEntry( - "An error occurred while loading test configuration: " + e.getMessage() - ); - failDynamicNode(dynamicNodes, stage, e.getMessage()); - return dynamicNodes; - } - - Map functions; - Map functionNames; - Map nodeCandidates; - Map userSecrets; - - stage = Stage.FETCH_CLOUDIATOR; - try { - functions = getFunctionsMap(); - functionNames = getFunctionNames(); - nodeCandidates = getFaasNodeCandidates(); - userSecrets = providerService - .getCloudDefinitionsForAllProviders() - .stream() - .map(CloudDefinition::getCredential) - .collect(Collectors.toMap(Credential::getUser, Credential::getSecret)); - - } catch (Exception e) { - testReporter.publishEntry( - "An error occurred while fetching data from Cloudiator: " + e.getMessage() - ); - failDynamicNode(dynamicNodes, Stage.FETCH_CLOUDIATOR, e.getMessage()); - return dynamicNodes; - } - - for (FunctionTestConfiguration functionTestConfiguration : functionTests) { - stage = Stage.GATHER_FUNCTION_DATA; - - String canonicalFunctionName = functionTestConfiguration.getFunctionName(); - String functionIdentifier = canonicalFunctionName.toLowerCase(); - String functionName = functionNames.get(canonicalFunctionName); - - Function function = functions.get(functionIdentifier); - List functionNode = prepareFunctionTestCases( - functionTestConfiguration, - nodeCandidates, - userSecrets, - functionName, - function - ); - - dynamicNodes.add( - DynamicContainer.dynamicContainer( - functionTestConfiguration.getFunctionName(), - functionNode - ) - ); - } - stage = Stage.END; - return dynamicNodes; - } - - public static String createTestCaseDisplayName(String input, String expectedOutput) { - return "EVENT=" + input + ", EXPECTED OUTPUT=" + expectedOutput; - } - - public static AWSLambda buildAWSLambdaClient(String user, String secret, String location) { - BasicAWSCredentials credentials = new BasicAWSCredentials(user, secret); - - return AWSLambdaClientBuilder.standard() - .withCredentials(new AWSStaticCredentialsProvider(credentials)) - .withRegion(Regions.fromName(location)) - .build(); + return String.join(";", input, expectedOutput); } - public void executeAWSLambdaTestCase(AWSLambda awsLambda, TestCase testCase) { + public void executeAWSLambdaTestCase(AWSLambda awsLambda, String functionName, String event, String expectedOutput) { stage = Stage.TEST_EXECUTION; - testReporter.publishEntry("bleble"); InvokeRequest invokeRequest = new InvokeRequest() - .withFunctionName(testCase.getFunctionName()) - .withPayload(testCase.getEvent()); - - String resultString = null; + .withFunctionName(functionName) + .withPayload(event); try { stage = Stage.AWS_INVOKE; InvokeResult result = awsLambda.invoke(invokeRequest); - resultString = new String(result.getPayload().array(), StandardCharsets.UTF_8); + String resultString = new String(result.getPayload().array(), StandardCharsets.UTF_8); + assertEquals(expectedOutput, resultString); } catch (ServiceException e) { fail("An exception while fetching response from AWS Lambda Client occurred"); } - assertEquals(testCase.getExpectedOutput(), resultString); + } public void executeAzureTestCase( String endpoint, String functionKey, - TestCase testCase + String event, + String expectedOutput ) throws Exception { stage = Stage.TEST_EXECUTION; String response = AzureFunctionsService.invokeFunction( endpoint, functionKey, - testCase.getEvent() + event ); - assertEquals(testCase.getExpectedOutput(), response); + assertEquals(expectedOutput, response); + } + + + private void ignoreTestCases(List functionNode, List testCases){ + testCases.stream().map( + testCase -> DynamicTest.dynamicTest( + createTestCaseDisplayName( + testCase.getEvent(), + testCase.getExpectedOutput() + ), + () -> fail("Test case ignored") + ) + ).forEach(functionNode::add); } @Ignore("Ignored test case") 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 index 64267bef1..5976da035 100644 --- 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 @@ -1,6 +1,6 @@ package eu.functionizer.functionizertestingtool.service.yaml; -import eu.functionizer.functionizertestingtool.model.test.TestConfiguration; +import eu.functionizer.functionizertestingtool.model.TestConfiguration; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; diff --git a/functionizer-testing-tool/src/main/resources/logback.xml b/functionizer-testing-tool/src/main/resources/logback.xml new file mode 100644 index 000000000..b5d5e707f --- /dev/null +++ b/functionizer-testing-tool/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %date{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) from %yellow(%logger) in %thread - %message%n%xException + + + + + + + -- GitLab From 5dd1b48c884a303ee6b097cec02fc823d6e704f7 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Wed, 3 Jun 2020 17:37:46 +0200 Subject: [PATCH 07/18] funtionizer tt: test reporting refactor and test result cleanup --- .../FunctionizerTestingToolController.java | 7 +- .../model/FunctionTestResult.java | 56 ++++ .../model/FunctionizerTestResult.java | 23 ++ .../model/ReportEntryKey.java | 10 + .../model/TestCaseResult.java | 13 + .../model/TestResultEnum.java | 19 ++ .../service/TestRunner.java | 12 +- .../service/test/FunctionizerReportData.java | 227 +++++++------- .../test/FunctionizerTestListener.java | 47 +-- .../test/ServerlessFunctionTestFactory.java | 276 ++++++++++++------ .../service/yaml/TestConfigurationLoader.java | 72 ++++- .../src/main/resources/logback.xml | 11 - 12 files changed, 487 insertions(+), 286 deletions(-) create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestResult.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionizerTestResult.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/ReportEntryKey.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCaseResult.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestResultEnum.java delete mode 100644 functionizer-testing-tool/src/main/resources/logback.xml 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 index ab096c42d..dc8ff3955 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java @@ -1,15 +1,13 @@ package eu.functionizer.functionizertestingtool; +import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult; import eu.functionizer.functionizertestingtool.service.TestRunner; -import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; 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.*; -import java.util.Collection; -import java.util.Map; @RestController @Slf4j @@ -25,12 +23,13 @@ public class FunctionizerTestingToolController { } @GetMapping(value = "/health") + @ResponseStatus(HttpStatus.OK) public void health() { } @PostMapping("/test") @ResponseStatus(HttpStatus.OK) - public Collection runTests() { + public FunctionizerTestResult runTests() { return testRunner.runTests(); } 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 000000000..6e83036b0 --- /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 000000000..8dbd9978d --- /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 000000000..71b8d9995 --- /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 000000000..ef3c65b9e --- /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 000000000..4a2100edb --- /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 index 9ba8b1e62..409250532 100644 --- 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 @@ -2,6 +2,7 @@ package eu.functionizer.functionizertestingtool.service; import java.util.Collection; +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; @@ -26,7 +27,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass @AllArgsConstructor(onConstructor = @__(@Autowired)) public class TestRunner { - public Collection runTests() { + public FunctionizerTestResult runTests() { log.debug("Initiating Test Launcher"); Launcher launcher = LauncherFactory.create(); @@ -40,16 +41,15 @@ public class TestRunner { log.debug("Initiating Listener"); - FunctionizerTestListener listener = - new FunctionizerTestListener(); + FunctionizerTestListener listener = new FunctionizerTestListener(); log.debug("Registering Listener"); launcher.registerTestExecutionListeners(listener); launcher.execute(request); - log.debug("Generating Report"); + log.info("Generating Report"); FunctionizerReportData report = listener.getReport(); - - return report.getTestTree(); + log.info("Test run ended"); + return report.getTestResult(); } } 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 index 4f3df4459..471105000 100644 --- 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 @@ -1,8 +1,7 @@ package eu.functionizer.functionizertestingtool.service.test; -import com.fasterxml.jackson.annotation.JsonAutoDetect; +import eu.functionizer.functionizertestingtool.model.*; import org.junit.ComparisonFailure; -import org.junit.Test; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; @@ -16,38 +15,27 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; -import static java.util.Collections.emptyList; +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; - private int containersFoundCount; - private int testsFoundCount; - private int finishedTestCount; - private int skippedTestCount; public long timeFinished; - public int containersFound; - public int testsFound; - public int containersStarted; - public int testsStarted; - public int containersSucceeded; - public int testsSucceeded; - public int containersAborted; - public int testsAborted; - public int containersFailed; - public int testsFailed; private List failures = new ArrayList<>(); - private TestTreeEntity testResults; + 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 final Map> reportEntries = new ConcurrentHashMap<>(); + + private TestIdentifier root; + private final Map> reportEntries = new ConcurrentHashMap<>(); private final TestPlan testPlan; private final Clock clock; @@ -56,34 +44,10 @@ public class FunctionizerReportData { this.testPlan = testPlan; this.clock = clock; - containersFoundCount = 0; - testsFoundCount = 0; - finishedTestCount = 0; - skippedTestCount = 0; - containersFound = 0; - testsFound = 0; - containersStarted = 0; - testsStarted = 0; - containersSucceeded = 0; - testsSucceeded = 0; - containersAborted = 0; - testsAborted = 0; - containersFailed = 0; - testsFailed = 0; - - } - - TestPlan getTestPlan() { - return this.testPlan; - } - - Clock getClock() { - return this.clock; } void markSkipped(TestIdentifier testIdentifier, String reason) { this.skippedTests.put(testIdentifier, reason == null ? "" : reason); - skippedTestCount += 1L; } void markStarted(TestIdentifier testIdentifier) { @@ -93,18 +57,33 @@ public class FunctionizerReportData { 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(""); + String reason = result + .getThrowable() + .map(ExceptionUtils::readStackTrace) + .orElse(""); this.skippedTests.put(testIdentifier, reason); } else { this.finishedTests.put(testIdentifier, result); - this.finishedTestCount += 1L; } } void addReportEntry(TestIdentifier testIdentifier, ReportEntry entry) { - List entries = this.reportEntries.computeIfAbsent(testIdentifier, key -> new ArrayList<>()); - entries.add(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) { @@ -113,42 +92,35 @@ public class FunctionizerReportData { public static class Failure { String displayName; -// String event; -// String expectedOutput; -// String actualOutput; String cause; public Failure( String displayName, -// String event, -// String expectedOutput, -// String actualOutput, String cause ) { this.displayName = displayName; -// this.event = event; -// this.expectedOutput = expectedOutput; -// this.actualOutput = actualOutput; this.cause = cause; } } - boolean wasSkipped(TestIdentifier testIdentifier) { - return findSkippedAncestor(testIdentifier).isPresent(); - } - TestExecutionResult getResult(TestIdentifier testIdentifier) { if (this.finishedTests.containsKey(testIdentifier)) { return this.finishedTests.get(testIdentifier); } Optional parent = this.testPlan.getParent(testIdentifier); - Optional ancestor = findAncestor(parent, this.finishedTests::containsKey); - if (ancestor.isPresent()) { - TestExecutionResult result = this.finishedTests.get(ancestor.get()); - if (result.getStatus() != SUCCESSFUL) { - return result; + 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; } @@ -158,17 +130,11 @@ public class FunctionizerReportData { return Duration.between(startInstant, endInstant).toMillis() / (double) MILLIS_PER_SECOND; } - List getReportEntries(TestIdentifier testIdentifier) { - return this.reportEntries.getOrDefault(testIdentifier, emptyList()); - } - - private Optional findSkippedAncestor(TestIdentifier testIdentifier) { - return findAncestor(Optional.of(testIdentifier), this.skippedTests::containsKey); - } - - private Optional findAncestor(Optional testIdentifier, - Predicate predicate) { - Optional current = testIdentifier; + private Optional findAncestor( + TestIdentifier testIdentifier, + Predicate predicate + ) { + Optional current = Optional.of(testIdentifier); while (current.isPresent()) { if (predicate.test(current.get())) { return current; @@ -178,61 +144,74 @@ public class FunctionizerReportData { return Optional.empty(); } - public void addChildrenToTree(TestTreeEntity entity, TestIdentifier parent) { - testPlan.getChildren(parent).forEach(child -> { - TestTreeEntity childEntity = new TestTreeEntity(child, getResult(child)); - entity.addChild(childEntity); - addChildrenToTree(childEntity, child); - } - ); + public FunctionizerTestResult getTestResult() { + return this.testResult; } - public Collection getTestTree() { - List testTreeEntities = new ArrayList<>(); - testPlan.getRoots().forEach(testIdentifier -> { - TestTreeEntity entity = new TestTreeEntity(testIdentifier, getResult(testIdentifier)); - testTreeEntities.add(entity); - addChildrenToTree(entity, testIdentifier); - }); + public void prepareTestResult() { + this.testResult = new FunctionizerTestResult(); + this.testResult.setDuration(getDurationInSeconds(this.root)); - return testTreeEntities; - } + 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; + } - @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) - public class TestTreeEntity { - private String displayName; - private String status; - private String event; - private String expectedOutput = ""; - private String actualOutput = ""; - private String message = ""; - private double duration; - private List children; - - public TestTreeEntity(TestIdentifier identifier, TestExecutionResult result) { - this.displayName = identifier.getDisplayName(); - String[] eventExpectedOutput = this.displayName.split(";"); - if (eventExpectedOutput.length == 2) { - this.event = eventExpectedOutput[0]; - this.expectedOutput = eventExpectedOutput[1]; + 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); } - - this.status = result.getStatus().toString(); - result.getThrowable().ifPresent(error -> { - this.message = error.getMessage(); - if (error instanceof ComparisonFailure) { - ComparisonFailure comparisonFailure = (ComparisonFailure) error; - this.expectedOutput = comparisonFailure.getExpected(); - this.actualOutput = comparisonFailure.getActual(); + 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); } - }); - this.duration = getDurationInSeconds(identifier); - this.children = new ArrayList<>(); - } + 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); + } - public void addChild(TestTreeEntity child) { - this.children.add(child); + 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 index 3b0870a98..d6a66f5c5 100644 --- 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 @@ -16,8 +16,6 @@ public class FunctionizerTestListener implements TestExecutionListener { private final Clock clock; - private TestPlan testPlan; - private FunctionizerReportData reportData; public FunctionizerTestListener() { @@ -34,47 +32,26 @@ public class FunctionizerTestListener implements TestExecutionListener { @Override public void testPlanExecutionStarted(TestPlan testPlan) { - this.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) { - if (testIdentifier.isContainer()) { - this.reportData.containersFound += 1; - } - if (testIdentifier.isTest()) { - this.reportData.testsFound += 1; - } } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { - // @formatter:off - long skippedContainers = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) - .filter(TestIdentifier::isContainer) - .count(); - long skippedTests = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) - .filter(TestIdentifier::isTest) - .count(); - // @formatter:on - this.reportData.markSkipped(testIdentifier, reason); } @Override public void executionStarted(TestIdentifier testIdentifier) { - if (testIdentifier.isContainer()) { - this.reportData.containersStarted += 1; - } - if (testIdentifier.isTest()) { - this.reportData.testsStarted += 1; - } this.reportData.markStarted(testIdentifier); } @@ -88,33 +65,13 @@ public class FunctionizerTestListener implements TestExecutionListener { switch (testExecutionResult.getStatus()) { - case SUCCESSFUL: { - if (testIdentifier.isContainer()) { - this.reportData.containersSucceeded += 1; - } - if (testIdentifier.isTest()) { - this.reportData.testsSucceeded += 1; - } - break; - } + case SUCCESSFUL: case ABORTED: { - if (testIdentifier.isContainer()) { - this.reportData.containersAborted += 1; - } - if (testIdentifier.isTest()) { - this.reportData.testsAborted += 1; - } break; } case FAILED: { - if (testIdentifier.isContainer()) { - this.reportData.containersFailed += 1; - } - if (testIdentifier.isTest()) { - this.reportData.testsFailed += 1; - } testExecutionResult.getThrowable().ifPresent( throwable -> this.reportData.addFailure(testIdentifier, throwable)); break; 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 index b9e365f01..b352001e9 100644 --- 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 @@ -17,6 +17,7 @@ import com.microsoft.azure.management.Azure; import eu.functionizer.functionizertestingtool.model.CloudiatorData; import eu.functionizer.functionizertestingtool.model.FunctionTestConfiguration; +import eu.functionizer.functionizertestingtool.model.ReportEntryKey; import eu.functionizer.functionizertestingtool.model.TestCase; import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; @@ -41,12 +42,12 @@ import io.github.cloudiator.rest.api.*; import io.github.cloudiator.rest.model.*; import lombok.extern.slf4j.Slf4j; -import org.junit.Ignore; import org.junit.jupiter.api.*; import org.springframework.web.server.ResponseStatusException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; @Slf4j public class ServerlessFunctionTestFactory { @@ -54,13 +55,14 @@ 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 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_AWS_CLIENT("BUILD AWS LAMBDA CLIENT"), BUILD_AZURE_CLIENT("BUILD AZURE CLIENT"), GET_AZURE_FUNCTION_KEY("GET AZURE FUNCTION KEY"), TEST_EXECUTION("TEST CASE EXECUTION"), @@ -85,6 +87,8 @@ public class ServerlessFunctionTestFactory { private static ProviderService providerService; private static Stage stage = null; + TestReporter testReporter; + @BeforeAll static void setUp() throws IOException { @@ -134,22 +138,35 @@ public class ServerlessFunctionTestFactory { } } - private void failDynamicNode( - List dynamicNode, Stage stage, String cause - ) { - log.info("Dynamic Node construction failed at the stage " + stage.getName()); + @BeforeEach + private void init(TestReporter testReporter) { + this.testReporter = testReporter; + } + + private 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); - dynamicNode.add(DynamicTest.dynamicTest( - "Dynamic node construction failed at the stage " + stage.getName(), - () -> fail(cause) - )); } @TestFactory + @DisplayName(ALL_TESTS_DISPLAY_NAME) public Collection dynamicTestNodes() { log.info("Test Factory started"); stage = Stage.START; + Map entry = createReportEntry(ALL_TESTS_DISPLAY_NAME); + entry.put(ReportEntryKey.ROOT, "true"); + testReporter.publishEntry(entry); + List dynamicNodes = new ArrayList<>(); List functionTests; @@ -159,8 +176,8 @@ public class ServerlessFunctionTestFactory { functionTests = TestConfigurationLoader .loadTestConfiguration() .getTests(); - } catch (ResponseStatusException e) { - failDynamicNode(dynamicNodes, stage, e.getMessage()); + } catch (Exception e) { + failDynamicNode(ALL_TESTS_DISPLAY_NAME, stage, e.getMessage()); return dynamicNodes; } @@ -171,7 +188,8 @@ public class ServerlessFunctionTestFactory { try { cloudiatorData = fetchCloudiatorData(); } catch (Exception e) { - failDynamicNode(dynamicNodes, stage, e.getMessage()); + testReporter.publishEntry("result", "fail"); + failDynamicNode(ALL_TESTS_DISPLAY_NAME, stage, e.getMessage()); return dynamicNodes; } @@ -183,7 +201,7 @@ public class ServerlessFunctionTestFactory { log.info("Gathering data on Function {}", functionConfigurationName); stage = Stage.GATHER_FUNCTION_DATA; - log.info("Looking up Function in the deployed Function list"); + log.debug("Looking up Function in the deployed Function list"); Function function = cloudiatorData .getDeployedFunctions() .get(functionConfigurationName.toLowerCase()); @@ -191,12 +209,9 @@ public class ServerlessFunctionTestFactory { List functionNode; if (function == null) { - log.info( - "Function {} not found in the deployed Function list. All test cases will be ignored", - functionConfigurationName - ); functionNode = new ArrayList<>(); - ignoreTestCases(functionNode, functionTestConfiguration.getTestCases()); + failDynamicNode(functionConfigurationName, stage, "Not found in the deployed Function list"); + ignoreTestCases(functionNode, functionTestConfiguration); } else { String functionDeployedName = cloudiatorData @@ -207,7 +222,7 @@ public class ServerlessFunctionTestFactory { .getNodeCandidates() .get(function.getId()); - log.info("Collecting test cases"); + log.debug("Collecting test cases"); functionNode = prepareFunctionNode( functionTestConfiguration, nodeCandidate, @@ -219,7 +234,7 @@ public class ServerlessFunctionTestFactory { dynamicNodes.add( DynamicContainer.dynamicContainer( - functionTestConfiguration.getFunctionName(), + functionConfigurationName, functionNode ) ); @@ -231,16 +246,16 @@ public class ServerlessFunctionTestFactory { private CloudiatorData fetchCloudiatorData( ) throws ApiException { - log.info("Fetching Functions from Cloudiator"); + log.debug("Fetching Functions from Cloudiator"); Map functions = getFunctionsMap(); - log.info("Fetching Function Names from Cloudiator"); + log.debug("Fetching Function Names from Cloudiator"); Map functionDeployNames = getFunctionDeployNames(); - log.info("Fetching Node Candidates from Cloudiator"); + log.debug("Fetching Node Candidates from Cloudiator"); Map nodeCandidates = getFaasNodeCandidates(); - log.info("Fetching Cloud Provider Credentials"); + log.debug("Fetching Cloud Provider Credentials"); stage = Stage.FETCH_CREDENTIALS; Map userSecrets = getCredentials(); @@ -350,6 +365,12 @@ public class ServerlessFunctionTestFactory { .collect(Collectors.toMap(Credential::getUser, Credential::getSecret)); } + private Map createReportEntry(String displayName) { + Map reportEntry = new HashMap<>(); + reportEntry.put("displayName", displayName); + return reportEntry; + } + private List prepareFunctionNode( FunctionTestConfiguration functionTestConfiguration, NodeCandidate nodeCandidate, @@ -363,14 +384,14 @@ public class ServerlessFunctionTestFactory { if (cloud == null) { log.info("Cloud not found. Omitting test cases"); failDynamicNode( - functionNode, - Stage.GATHER_FUNCTION_DATA, - "Cloud not found" + functionTestConfiguration.getFunctionName(), + Stage.GATHER_FUNCTION_DATA,"Cloud not found" ); + ignoreTestCases(functionNode, functionTestConfiguration); return functionNode; } - log.info("Retrieving Cloud credentials"); + log.debug("Retrieving Cloud credentials"); String user = Objects.requireNonNull(nodeCandidate.getCloud()) .getCredential() .getUser(); @@ -381,7 +402,7 @@ public class ServerlessFunctionTestFactory { switch (provider) { case AWS_EC2: - log.debug("Building AWS Function name"); + log.debug("Building AWS Lambda Function name"); String awsFunctionName = String.join( "-", function.getStackId(), @@ -395,30 +416,20 @@ public class ServerlessFunctionTestFactory { Objects.requireNonNull(Objects.requireNonNull( nodeCandidate .getLocation()) - .getParent()) + .getParent()) .getProviderId() ); - functionTestConfiguration.getTestCases().forEach(testCase -> { - - log.debug( - "Creating test case: event={}, expected output={}", - testCase.getEvent(), - testCase.getExpectedOutput() - ); - functionNode.add(DynamicTest.dynamicTest( - createTestCaseDisplayName( - testCase.getEvent(), - testCase.getExpectedOutput() - ), - () -> executeAWSLambdaTestCase( - awsLambdaClient, - awsFunctionName, - testCase.getEvent(), - testCase.getExpectedOutput() - ) - )); - }); + List awsTests = functionTestConfiguration.getTestCases() + .stream() + .map(testCase -> createAWSLambdaTest( + awsLambdaClient, + functionTestConfiguration.getFunctionName(), + awsFunctionName, + testCase + )) + .collect(Collectors.toList()); + functionNode.addAll(awsTests); break; case AZURE: @@ -434,7 +445,11 @@ public class ServerlessFunctionTestFactory { stage = Stage.GET_AZURE_FUNCTION_KEY; functionKey = AzureFunctionsService.getFunctionKey(azureClient, function); } catch (Exception e) { - failDynamicNode(functionNode, stage, e.getMessage()); + failDynamicNode + (functionTestConfiguration.getFunctionName(), + stage, + e.getMessage() + ); break; } @@ -445,41 +460,116 @@ public class ServerlessFunctionTestFactory { ); log.info("Function URL = {}", azureFunctionEndpoint); - functionTestConfiguration.getTestCases().forEach(testCase -> { - testCase.setFunctionName(functionDeployedName); - - log.debug( - "Creating test case: event={}, expected output={}", - testCase.getEvent(), - testCase.getExpectedOutput() - ); - functionNode.add(DynamicTest.dynamicTest( - createTestCaseDisplayName( - testCase.getEvent(), - testCase.getExpectedOutput() - ), - () -> executeAzureTestCase( - azureFunctionEndpoint, - functionKey, - testCase.getEvent(), - testCase.getExpectedOutput() - ) - )); - }); + List azureTests = functionTestConfiguration + .getTestCases() + .stream() + .map(testCase -> createAzureTest( + functionTestConfiguration.getFunctionName(), + azureFunctionEndpoint, + functionKey, + testCase + )) + .collect(Collectors.toList()); + + functionNode.addAll(azureTests); break; default: - log.info("Function {} has been deployed on unsupported Cloud. Omitting", functionDeployedName); - ignoreTestCases(functionNode, functionTestConfiguration.getTestCases()); + log.info( + "Function {} has been deployed on unsupported Cloud. Omitting", + functionDeployedName + ); + ignoreTestCases(functionNode, functionTestConfiguration); } return functionNode; } - public static String createTestCaseDisplayName(String input, String expectedOutput) { - return String.join(";", input, expectedOutput); + private static String createTestCaseDisplayName( + String functionName, + String input, + String expectedOutput + ) { + return String.format( + "Function %s invoked with %s should return %s", + functionName, + input, + expectedOutput + ); + } + + private 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() + ) + ); + } + + private DynamicTest createAzureTest( + String functionName, + String azureFunctionEndpoint, + String functionKey, + 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, + () -> executeAzureTestCase( + azureFunctionEndpoint, + functionKey, + testCase.getEvent(), + testCase.getExpectedOutput() + ) + ); } - public void executeAWSLambdaTestCase(AWSLambda awsLambda, String functionName, String event, String expectedOutput) { + public void executeAWSLambdaTestCase( + AWSLambda awsLambda, + String functionName, + String event, + String expectedOutput + ) { stage = Stage.TEST_EXECUTION; InvokeRequest invokeRequest = new InvokeRequest() @@ -512,21 +602,19 @@ public class ServerlessFunctionTestFactory { assertEquals(expectedOutput, response); } - - private void ignoreTestCases(List functionNode, List testCases){ - testCases.stream().map( - testCase -> DynamicTest.dynamicTest( - createTestCaseDisplayName( - testCase.getEvent(), - testCase.getExpectedOutput() - ), - () -> fail("Test case ignored") - ) - ).forEach(functionNode::add); - } - - @Ignore("Ignored test case") - public void ignoredTestCase() { - fail("Ignored"); + private 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/yaml/TestConfigurationLoader.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/yaml/TestConfigurationLoader.java index 5976da035..f49492fc4 100644 --- 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 @@ -1,7 +1,10 @@ package eu.functionizer.functionizertestingtool.service.yaml; +import eu.functionizer.functionizertestingtool.model.FunctionTestConfiguration; +import eu.functionizer.functionizertestingtool.model.TestCase; import eu.functionizer.functionizertestingtool.model.TestConfiguration; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @@ -9,6 +12,10 @@ import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; import java.io.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @Service @Slf4j @@ -17,11 +24,12 @@ public class TestConfigurationLoader { private final static String CONFIG_FILE_PATH = System.getenv("MELODIC_CONFIG_DIR") + "/tests.yml"; - public static TestConfiguration loadTestConfiguration() throws ResponseStatusException { + 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))) { - return yaml.load(fileInputStream); + configuration = yaml.load(fileInputStream); } catch (FileNotFoundException e) { String errorMessage = String.format( @@ -38,6 +46,66 @@ public class TestConfigurationLoader { log.error(errorMessage, e); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); } + checkTestConfiguration(configuration); + return configuration; + } + + private static void checkTestConfiguration(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() + ); + } + } + + private static void checkFunctionNamesUniqueness( + List functionTestConfigurations + ) throws Exception { + 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 Exception(String.format( + "Function name '%s' occurred in more than one test. Please adjust the config file", + name + )); + } + } + } + + private static void checkTestCasesUniqueness(List testCases, String functionName) throws Exception { + 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 Exception(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", + eventExpectedOutput.getKey(), + eventExpectedOutput.getValue(), + functionName + )); + } + } } private void saveTestConfiguration(TestConfiguration testConfiguration) { diff --git a/functionizer-testing-tool/src/main/resources/logback.xml b/functionizer-testing-tool/src/main/resources/logback.xml deleted file mode 100644 index b5d5e707f..000000000 --- a/functionizer-testing-tool/src/main/resources/logback.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - %date{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) from %yellow(%logger) in %thread - %message%n%xException - - - - - - - -- GitLab From 8fd05a9af6bd4d79e59145db9893d6c402f17b5c Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Mon, 8 Jun 2020 19:10:32 +0200 Subject: [PATCH 08/18] ftt: move ftt's dependencies from gui-backend to melodic-commons --- functionizer-testing-tool/pom.xml | 50 +++----------- .../functionizertestingtool/WebSecurity.java | 1 - .../model/TestConfiguration.java | 1 - .../service/TestRunner.java | 2 - .../test/ServerlessFunctionTestFactory.java | 23 +++---- .../service/yaml/TestConfigurationLoader.java | 2 +- .../guibackend/ApplicationContext.java | 69 +++++++++++++++++++ .../deployment/CloudDefinitionRequest.java | 2 +- .../application/ApplicationController.java | 2 +- .../controller/byon/ByonController.java | 6 +- .../deployment/DeploymentController.java | 4 +- .../deployment/request/DeploymentRequest.java | 2 +- .../response/UploadXmiResponse.java | 2 +- .../controller/process/ProcessController.java | 2 +- .../provider/ProviderController.java | 6 +- .../controller/GuiExceptionHandler.java | 2 +- .../guibackend/model/provider/CloudType.java | 5 -- .../service/byon/ByonIdCreatorService.java | 4 +- .../guibackend/service/byon/ByonMapper.java | 6 +- .../guibackend/service/byon/ByonService.java | 12 ++-- .../service/deployment/DeploymentMapper.java | 2 +- .../service/deployment/DeploymentService.java | 6 +- melodic-commons/pom.xml | 26 +++++++ .../commons}/cloudiator/CloudiatorApi.java | 2 +- .../cloudiator/CloudiatorClientApi.java | 4 +- .../commons}/cloudiator/QueueInspector.java | 2 +- .../CloudDefinitionNotFoundException.java | 2 +- .../SecureVariableNotFoundException.java | 2 +- .../exception/ValidationException.java | 2 +- .../upperware/commons/model}/GuiYamlData.java | 6 +- .../commons/model}/SecureVariable.java | 2 +- .../commons}/model/byon/ByonDefinition.java | 2 +- .../commons}/model/byon/ByonEnums.java | 2 +- .../commons}/model/byon/GeoLocation.java | 2 +- .../commons}/model/byon/IpAddress.java | 2 +- .../commons}/model/byon/LoginCredential.java | 2 +- .../commons}/model/byon/NodeProperties.java | 2 +- .../commons}/model/byon/OperatingSystem.java | 2 +- .../commons}/model/provider/Api.java | 2 +- .../model/provider/CloudConfiguration.java | 2 +- .../model/provider/CloudDefinition.java | 2 +- .../commons/model/provider/CloudType.java | 5 ++ .../commons}/model/provider/Credential.java | 2 +- .../model/provider/ParentProperty.java | 2 +- .../commons}/model/provider/Provider.java | 2 +- .../model/provider/ProviderEnums.java | 2 +- .../model/provider/SingleProperty.java | 2 +- .../provider/ProviderIdCreatorService.java | 8 +-- .../service/provider/ProviderService.java | 18 ++--- .../provider/ProviderValidationService.java | 6 +- .../service}/store/SecureStoreService.java | 16 ++--- .../service/yaml/YamlDataService.java | 8 +-- .../src/main/resources/gui-data.yaml | 2 +- 53 files changed, 206 insertions(+), 146 deletions(-) delete mode 100644 gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudType.java rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication => melodic-commons/src/main/java/eu/passage/upperware/commons}/cloudiator/CloudiatorApi.java (93%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication => melodic-commons/src/main/java/eu/passage/upperware/commons}/cloudiator/CloudiatorClientApi.java (98%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication => melodic-commons/src/main/java/eu/passage/upperware/commons}/cloudiator/QueueInspector.java (97%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/exception/CloudDefinitionNotFoundException.java (88%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/exception/SecureVariableNotFoundException.java (88%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/exception/ValidationException.java (85%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend/properties => melodic-commons/src/main/java/eu/passage/upperware/commons/model}/GuiYamlData.java (63%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/common => melodic-commons/src/main/java/eu/passage/upperware/commons/model}/SecureVariable.java (78%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/byon/ByonDefinition.java (88%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/byon/ByonEnums.java (84%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/byon/GeoLocation.java (83%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/byon/IpAddress.java (83%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/byon/LoginCredential.java (84%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/byon/NodeProperties.java (88%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/byon/OperatingSystem.java (84%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/provider/Api.java (81%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/provider/CloudConfiguration.java (81%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/provider/CloudDefinition.java (84%) create mode 100644 melodic-commons/src/main/java/eu/passage/upperware/commons/model/provider/CloudType.java rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/provider/Credential.java (82%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/provider/ParentProperty.java (80%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/provider/Provider.java (82%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/provider/ProviderEnums.java (79%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/model/provider/SingleProperty.java (83%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/service/provider/ProviderIdCreatorService.java (95%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/service/provider/ProviderService.java (93%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/service/provider/ProviderValidationService.java (95%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/secure => melodic-commons/src/main/java/eu/passage/upperware/commons/service}/store/SecureStoreService.java (91%) rename {gui-backend/src/main/java/eu/melodic/upperware/guibackend => melodic-commons/src/main/java/eu/passage/upperware/commons}/service/yaml/YamlDataService.java (90%) rename {gui-backend => melodic-commons}/src/main/resources/gui-data.yaml (95%) diff --git a/functionizer-testing-tool/pom.xml b/functionizer-testing-tool/pom.xml index c53d4340c..36111ed63 100644 --- a/functionizer-testing-tool/pom.xml +++ b/functionizer-testing-tool/pom.xml @@ -69,10 +69,8 @@ - eu.melodic - gui-backend - 3.1.0-SNAPSHOT - compile + org.ow2.paasage + melodic-commons @@ -152,16 +150,15 @@ junit-platform-reporting 1.6.2 - - org.jetbrains - annotations - RELEASE - compile - + + io.github.cloudiator.client + java-rest + - + + ${project.artifactId} org.springframework.boot @@ -182,36 +179,9 @@ gmaven-plugin - - org.apache.maven.plugins - maven-dependency-plugin - 3.1.2 - - - copy - package - - copy - - - - - [ groupId ] - [ artifactId ] - [ version ] - [ packaging ] - [classifier - optional] - [ true or false ] - [ output directory ] - [ filename ] - - - - - - - + + 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 index 0ba220231..0b3966ff6 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/WebSecurity.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/WebSecurity.java @@ -11,7 +11,6 @@ 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; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @Slf4j @EnableWebSecurity diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java index 6dd324ab9..1610c77a8 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java @@ -1,6 +1,5 @@ package eu.functionizer.functionizertestingtool.model; - import lombok.Data; import java.util.List; 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 index 409250532..89a62839c 100644 --- 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 @@ -1,7 +1,5 @@ package eu.functionizer.functionizertestingtool.service; -import java.util.Collection; - import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult; import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; import eu.functionizer.functionizertestingtool.service.test.FunctionizerTestListener; 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 index b352001e9..0a0009b3d 100644 --- 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 @@ -22,17 +22,17 @@ import eu.functionizer.functionizertestingtool.model.TestCase; import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoader; -import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; -import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorClientApi; -import eu.melodic.upperware.guibackend.communication.cloudiator.QueueInspector; -import eu.melodic.upperware.guibackend.model.provider.CloudDefinition; -import eu.melodic.upperware.guibackend.model.provider.Credential; -import eu.melodic.upperware.guibackend.model.provider.Provider; -import eu.melodic.upperware.guibackend.service.provider.ProviderIdCreatorService; -import eu.melodic.upperware.guibackend.service.provider.ProviderService; -import eu.melodic.upperware.guibackend.service.provider.ProviderValidationService; -import eu.melodic.upperware.guibackend.service.secure.store.SecureStoreService; -import eu.melodic.upperware.guibackend.service.yaml.YamlDataService; +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; @@ -43,7 +43,6 @@ import io.github.cloudiator.rest.model.*; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.*; -import org.springframework.web.server.ResponseStatusException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; 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 index f49492fc4..bb8d4ebda 100644 --- 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 @@ -22,7 +22,7 @@ import java.util.stream.Collectors; public class TestConfigurationLoader { private final static String CONFIG_FILE_PATH = - System.getenv("MELODIC_CONFIG_DIR") + "/tests.yml"; + System.getenv("MELODIC_CONFIG_DIR") + "/tests/tests.yml"; public static TestConfiguration loadTestConfiguration() throws Exception { Yaml yaml = new Yaml(new Constructor(TestConfiguration.class)); 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 fba45c546..b4538bb20 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,9 +3,18 @@ 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 io.github.cloudiator.rest.model.Cloud; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.SimpleClientHttpRequestFactory; @@ -68,8 +77,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/mule/deployment/CloudDefinitionRequest.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/mule/deployment/CloudDefinitionRequest.java index eb0d67d9b..756b1ab25 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/controller/application/ApplicationController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/application/ApplicationController.java index 7b5a4b1c6..7a3086f7c 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/application/ApplicationController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/application/ApplicationController.java @@ -1,6 +1,6 @@ package eu.melodic.upperware.guibackend.controller.application; -import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; +import eu.passage.upperware.commons.cloudiator.CloudiatorApi; import io.github.cloudiator.rest.model.Function; import io.github.cloudiator.rest.model.Node; import lombok.AllArgsConstructor; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/byon/ByonController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/byon/ByonController.java index 9454cbdef..2ba3df224 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/byon/ByonController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/byon/ByonController.java @@ -1,8 +1,8 @@ package eu.melodic.upperware.guibackend.controller.byon; -import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; -import eu.melodic.upperware.guibackend.model.byon.ByonDefinition; -import eu.melodic.upperware.guibackend.model.byon.ByonEnums; +import eu.passage.upperware.commons.cloudiator.CloudiatorApi; +import eu.passage.upperware.commons.model.byon.ByonDefinition; +import eu.passage.upperware.commons.model.byon.ByonEnums; import eu.melodic.upperware.guibackend.service.byon.ByonService; import io.github.cloudiator.rest.model.ByonNode; import lombok.AllArgsConstructor; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/DeploymentController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/DeploymentController.java index 9a6a97cc2..7d743a30a 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/DeploymentController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/DeploymentController.java @@ -1,12 +1,12 @@ package eu.melodic.upperware.guibackend.controller.deployment; import eu.melodic.upperware.guibackend.controller.common.MelodicHeaders; -import eu.melodic.upperware.guibackend.controller.deployment.common.SecureVariable; +import eu.passage.upperware.commons.model.SecureVariable; import eu.melodic.upperware.guibackend.controller.deployment.request.DeploymentRequest; import eu.melodic.upperware.guibackend.controller.deployment.response.DeploymentResponse; import eu.melodic.upperware.guibackend.controller.deployment.response.UploadXmiResponse; import eu.melodic.upperware.guibackend.service.deployment.DeploymentService; -import eu.melodic.upperware.guibackend.service.secure.store.SecureStoreService; +import eu.passage.upperware.commons.service.store.SecureStoreService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/request/DeploymentRequest.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/request/DeploymentRequest.java index 9b4f4f8bf..b0e2173a2 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/request/DeploymentRequest.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/request/DeploymentRequest.java @@ -1,6 +1,6 @@ package eu.melodic.upperware.guibackend.controller.deployment.request; -import eu.melodic.upperware.guibackend.model.provider.CloudDefinition; +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/response/UploadXmiResponse.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/response/UploadXmiResponse.java index c1f2c6ed5..c470334ce 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/response/UploadXmiResponse.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/deployment/response/UploadXmiResponse.java @@ -1,6 +1,6 @@ package eu.melodic.upperware.guibackend.controller.deployment.response; -import eu.melodic.upperware.guibackend.controller.deployment.common.SecureVariable; +import eu.passage.upperware.commons.model.SecureVariable; import lombok.*; import org.springframework.http.HttpStatus; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/process/ProcessController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/process/ProcessController.java index 4e85c778f..0aea5c92a 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/process/ProcessController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/process/ProcessController.java @@ -1,7 +1,7 @@ package eu.melodic.upperware.guibackend.controller.process; import eu.melodic.models.services.adapter.DifferenceResponse; -import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; +import eu.passage.upperware.commons.cloudiator.CloudiatorApi; import eu.melodic.upperware.guibackend.controller.process.response.CpModelResponse; import eu.melodic.upperware.guibackend.controller.process.response.CpSolutionResponse; import eu.melodic.upperware.guibackend.controller.process.response.ProcessInstanceResponse; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/provider/ProviderController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/provider/ProviderController.java index bc871e8cc..e7d8bfade 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/provider/ProviderController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/provider/ProviderController.java @@ -1,8 +1,8 @@ package eu.melodic.upperware.guibackend.controller.provider; -import eu.melodic.upperware.guibackend.model.provider.CloudDefinition; -import eu.melodic.upperware.guibackend.model.provider.ProviderEnums; -import eu.melodic.upperware.guibackend.service.provider.ProviderService; +import eu.passage.upperware.commons.model.provider.CloudDefinition; +import eu.passage.upperware.commons.model.provider.ProviderEnums; +import eu.passage.upperware.commons.service.provider.ProviderService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/controller/GuiExceptionHandler.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/controller/GuiExceptionHandler.java index 3fc356d97..3ebe1847c 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/controller/GuiExceptionHandler.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/exception/controller/GuiExceptionHandler.java @@ -1,7 +1,7 @@ package eu.melodic.upperware.guibackend.exception.controller; import eu.melodic.upperware.guibackend.exception.CamundaErrorVariableException; -import eu.melodic.upperware.guibackend.exception.SecureVariableNotFoundException; +import eu.passage.upperware.commons.exception.SecureVariableNotFoundException; import eu.melodic.upperware.guibackend.exception.response.CamundaVariableErrorResponse; import eu.melodic.upperware.guibackend.exception.response.MissingSecureVariableErrorResponse; import org.springframework.http.HttpStatus; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudType.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudType.java deleted file mode 100644 index 0c94b5dc0..000000000 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/model/provider/CloudType.java +++ /dev/null @@ -1,5 +0,0 @@ -package eu.melodic.upperware.guibackend.model.provider; - -public enum CloudType { - PRIVATE, PUBLIC -} diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonIdCreatorService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonIdCreatorService.java index d20cde8e6..b1e43d5e7 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonIdCreatorService.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonIdCreatorService.java @@ -1,7 +1,7 @@ package eu.melodic.upperware.guibackend.service.byon; -import eu.melodic.upperware.guibackend.model.byon.ByonDefinition; -import eu.melodic.upperware.guibackend.model.byon.IpAddress; +import eu.passage.upperware.commons.model.byon.ByonDefinition; +import eu.passage.upperware.commons.model.byon.IpAddress; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonMapper.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonMapper.java index a596bc594..d86a29b39 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonMapper.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonMapper.java @@ -1,6 +1,6 @@ package eu.melodic.upperware.guibackend.service.byon; -import eu.melodic.upperware.guibackend.model.byon.ByonDefinition; +import eu.passage.upperware.commons.model.byon.ByonDefinition; import io.github.cloudiator.rest.model.IpAddress; import io.github.cloudiator.rest.model.NewNode; import io.github.cloudiator.rest.model.NodeProperties; @@ -20,7 +20,7 @@ public class ByonMapper { return newNode; } - private NodeProperties mapGuiNodePropertiesToCloudiatorNodeProperties(eu.melodic.upperware.guibackend.model.byon.NodeProperties guiNodeProperties) { + private NodeProperties mapGuiNodePropertiesToCloudiatorNodeProperties(eu.passage.upperware.commons.model.byon.NodeProperties guiNodeProperties) { NodeProperties resultNodeProperties = new NodeProperties(); resultNodeProperties.setProviderId(guiNodeProperties.getProviderId()); resultNodeProperties.setNumberOfCores(guiNodeProperties.getNumberOfCores()); @@ -31,7 +31,7 @@ public class ByonMapper { return resultNodeProperties; } - private List mapGuiIpAddressesToCloudiatorIpAddresses(List ipAddresses) { + private List mapGuiIpAddressesToCloudiatorIpAddresses(List ipAddresses) { return ipAddresses.stream() .map(ipAddress -> (IpAddress) ipAddress) .collect(Collectors.toList()); diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonService.java index f701a1509..785072ec4 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonService.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/byon/ByonService.java @@ -1,12 +1,12 @@ package eu.melodic.upperware.guibackend.service.byon; -import eu.melodic.upperware.guibackend.communication.cloudiator.CloudiatorApi; +import eu.passage.upperware.commons.cloudiator.CloudiatorApi; import eu.melodic.upperware.guibackend.exception.ByonDefinitionNotFoundException; -import eu.melodic.upperware.guibackend.model.byon.ByonDefinition; -import eu.melodic.upperware.guibackend.model.byon.ByonEnums; -import eu.melodic.upperware.guibackend.model.byon.LoginCredential; -import eu.melodic.upperware.guibackend.service.secure.store.SecureStoreService; -import eu.melodic.upperware.guibackend.service.yaml.YamlDataService; +import eu.passage.upperware.commons.model.byon.ByonDefinition; +import eu.passage.upperware.commons.model.byon.ByonEnums; +import eu.passage.upperware.commons.model.byon.LoginCredential; +import eu.passage.upperware.commons.service.store.SecureStoreService; +import eu.passage.upperware.commons.service.yaml.YamlDataService; import io.github.cloudiator.rest.model.*; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/deployment/DeploymentMapper.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/deployment/DeploymentMapper.java index 40d2c3ee3..a3ed624ea 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/deployment/DeploymentMapper.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/deployment/DeploymentMapper.java @@ -8,7 +8,7 @@ import eu.melodic.upperware.guibackend.communication.mule.deployment.CloudConfig import eu.melodic.upperware.guibackend.communication.mule.deployment.CloudDefinitionRequest; import eu.melodic.upperware.guibackend.communication.mule.deployment.CredentialRequest; import eu.melodic.upperware.guibackend.controller.deployment.request.DeploymentRequest; -import eu.melodic.upperware.guibackend.model.provider.*; +import eu.passage.upperware.commons.model.provider.*; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/deployment/DeploymentService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/deployment/DeploymentService.java index 082d275ea..5e1ccdc96 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/deployment/DeploymentService.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/deployment/DeploymentService.java @@ -4,14 +4,14 @@ import eu.melodic.models.commons.Watermark; import eu.melodic.models.commons.WatermarkImpl; import eu.melodic.models.services.frontend.DeploymentProcessRequest; import eu.melodic.upperware.guibackend.communication.mule.MuleApi; -import eu.melodic.upperware.guibackend.controller.deployment.common.SecureVariable; +import eu.passage.upperware.commons.model.SecureVariable; import eu.melodic.upperware.guibackend.controller.deployment.request.DeploymentRequest; import eu.melodic.upperware.guibackend.controller.deployment.response.DeploymentResponse; import eu.melodic.upperware.guibackend.controller.deployment.response.UploadXmiResponse; import eu.melodic.upperware.guibackend.service.cdo.CdoService; import eu.melodic.upperware.guibackend.service.cdo.ModelNameGenerator; -import eu.melodic.upperware.guibackend.service.provider.ProviderService; -import eu.melodic.upperware.guibackend.service.secure.store.SecureStoreService; +import eu.passage.upperware.commons.service.provider.ProviderService; +import eu.passage.upperware.commons.service.store.SecureStoreService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.net4j.connector.ConnectorException; diff --git a/melodic-commons/pom.xml b/melodic-commons/pom.xml index 14b4207fc..aa9322aae 100644 --- a/melodic-commons/pom.xml +++ b/melodic-commons/pom.xml @@ -33,12 +33,24 @@ 1.7.25 + + 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/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 38771a210..28ac65d24 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 30b2e4cbe..e4fda2b91 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 aed5bd2cd..2fad9481d 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 061cf8d04..e5989c45e 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 c8a0d0239..76b3b349e 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 2bd3c0971..b5ca3734a 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 59f9aedce..04deefbcf 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 633d18764..7cfdbfe05 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 63b32d2a2..853785406 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 ab06575d9..be2212f75 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 7cc577584..dc5668481 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 5eb56663c..58cf61cef 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 2da0f48a1..5fff7b327 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 a8d837200..6cb0dd5b0 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 c66d75d82..f14c32d56 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 40699ee02..2e7e2d558 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 e8086c4c2..5af19758c 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 f85d88966..3df0545bf 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 000000000..228692b04 --- /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 2fc71011d..53fe5d876 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 10b63a53a..600462465 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 ef6d996ae..0577de4c1 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 eeb473155..91f3f8804 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 fd8626bd4..ca0f038a3 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/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 87d594171..a601ecefd 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 6c7eadbfe..aff89ae43 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 5c2f9204a..e188a6c4c 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 f37c34f6b..6f3455cf7 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/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 643d15b61..3db6ea783 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 a7f6a8007..17abda4c1 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} -- GitLab From b9d6d6b5ff65c119be18e0c2a080d88071420424 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Tue, 9 Jun 2020 16:43:13 +0200 Subject: [PATCH 09/18] ftt: set default trust store --- functionizer-testing-tool/src/main/docker/run.sh | 6 +++++- .../service/test/ServerlessFunctionTestFactory.java | 1 + .../main/resources/eu.functionizer.testingTool.properties | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 functionizer-testing-tool/src/main/resources/eu.functionizer.testingTool.properties diff --git a/functionizer-testing-tool/src/main/docker/run.sh b/functionizer-testing-tool/src/main/docker/run.sh index c03593e7e..be98ed070 100644 --- a/functionizer-testing-tool/src/main/docker/run.sh +++ b/functionizer-testing-tool/src/main/docker/run.sh @@ -1,3 +1,7 @@ #!/bin/sh -/config/wait-for-cdo.sh && java -Djavax.net.ssl.trustStore=/config/common/melodic-truststore.p12 -Djavax.net.ssl.trustStorePassword=melodic -Djavax.net.ssl.trustStoreType=pkcs12 -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 +/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/service/test/ServerlessFunctionTestFactory.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/ServerlessFunctionTestFactory.java index 0a0009b3d..732c71138 100644 --- 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 @@ -377,6 +377,7 @@ public class ServerlessFunctionTestFactory { String functionDeployedName, Function function ) { + log.info("Preparing Function Node"); List functionNode = new ArrayList<>(); Cloud cloud = nodeCandidate.getCloud(); 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 000000000..bfea1b24a --- /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 -- GitLab From 3d4a0a0a427e60a78e6ea895e221b581ebaa43d8 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Wed, 10 Jun 2020 17:09:13 +0200 Subject: [PATCH 10/18] ftt: more exception handling on AWS Lambda --- .../test/ServerlessFunctionTestFactory.java | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) 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 index 732c71138..b9be9cd9a 100644 --- 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 @@ -11,7 +11,6 @@ import java.util.stream.Stream; import com.amazonaws.services.lambda.AWSLambda; import com.amazonaws.services.lambda.model.InvokeRequest; import com.amazonaws.services.lambda.model.InvokeResult; -import com.amazonaws.services.lambda.model.ServiceException; import com.microsoft.azure.management.Azure; @@ -63,6 +62,7 @@ public class ServerlessFunctionTestFactory { 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"), @@ -402,6 +402,8 @@ public class ServerlessFunctionTestFactory { switch (provider) { case AWS_EC2: + AWSLambda awsLambdaClient; + log.debug("Building AWS Lambda Function name"); String awsFunctionName = String.join( "-", @@ -410,15 +412,22 @@ public class ServerlessFunctionTestFactory { ); log.debug("Building AWS Lambda client"); - AWSLambda awsLambdaClient = AWSLambdaService.buildClient( - user, - secret, - Objects.requireNonNull(Objects.requireNonNull( - nodeCandidate - .getLocation()) - .getParent()) - .getProviderId() - ); + stage = Stage.BUILD_AWS_LAMBDA_CLIENT; + try { + awsLambdaClient = AWSLambdaService.buildClient( + user, + secret, + Objects.requireNonNull(nodeCandidate.getLocation()) + .getProviderId() + ); + } catch (Exception e) { + failDynamicNode + (functionTestConfiguration.getFunctionName(), + stage, + e.getMessage() + ); + break; + } List awsTests = functionTestConfiguration.getTestCases() .stream() @@ -580,9 +589,20 @@ public class ServerlessFunctionTestFactory { stage = Stage.AWS_INVOKE; InvokeResult result = awsLambda.invoke(invokeRequest); String resultString = new String(result.getPayload().array(), StandardCharsets.UTF_8); - assertEquals(expectedOutput, resultString); - } catch (ServiceException e) { - fail("An exception while fetching response from AWS Lambda Client occurred"); + 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() + )); } } -- GitLab From d04b95f1212d61bc20ad7b990b108d4df5f9d023 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Tue, 16 Jun 2020 15:20:47 +0200 Subject: [PATCH 11/18] ftt: gui-backend upload test config file --- .../FunctionizerTestingToolController.java | 2 + .../service/TestRunner.java | 1 + .../service/yaml/TestConfigurationLoader.java | 4 +- .../controller/testing/TestingController.java | 35 ++++++++++ .../testing/UploadTestConfigResponse.java | 15 +++++ .../service/testing/TestingService.java | 66 +++++++++++++++++++ .../upperware/commons/MelodicConstants.java | 1 + 7 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java create mode 100644 gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java create mode 100644 gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java 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 index dc8ff3955..0aadc8cb2 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java @@ -1,7 +1,9 @@ package eu.functionizer.functionizertestingtool; +import eu.functionizer.functionizertestingtool.model.FunctionTestResult; import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult; import eu.functionizer.functionizertestingtool.service.TestRunner; +import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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 index 89a62839c..bea876ac6 100644 --- 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 @@ -1,5 +1,6 @@ package eu.functionizer.functionizertestingtool.service; +import eu.functionizer.functionizertestingtool.model.FunctionTestResult; import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult; import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; import eu.functionizer.functionizertestingtool.service.test.FunctionizerTestListener; 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 index bb8d4ebda..622e984c8 100644 --- 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 @@ -3,6 +3,7 @@ package eu.functionizer.functionizertestingtool.service.yaml; import eu.functionizer.functionizertestingtool.model.FunctionTestConfiguration; import eu.functionizer.functionizertestingtool.model.TestCase; import eu.functionizer.functionizertestingtool.model.TestConfiguration; +import eu.passage.upperware.commons.MelodicConstants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; import org.springframework.http.HttpStatus; @@ -21,8 +22,7 @@ import java.util.stream.Collectors; @Slf4j public class TestConfigurationLoader { - private final static String CONFIG_FILE_PATH = - System.getenv("MELODIC_CONFIG_DIR") + "/tests/tests.yml"; + 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)); diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java new file mode 100644 index 000000000..bbb0ab87b --- /dev/null +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java @@ -0,0 +1,35 @@ +package eu.melodic.upperware.guibackend.controller.testing; + +import eu.melodic.upperware.guibackend.service.testing.TestingService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/auth/test") +@Slf4j +@AllArgsConstructor +public class TestingController { + + TestingService testingService; + + @PostMapping(value = "/upload", consumes = {"multipart/form-data"}) + @ResponseStatus(HttpStatus.CREATED) + public UploadTestConfigResponse uploadTestConfigFile(@RequestParam("file") MultipartFile file) { + log.info( + "POST request for upload test configuration file with name: {}", + file.getResource().getFilename() + ); + String fileName = testingService.uploadTestConfig(file); + log.info("File {} successfully uploaded. Finding secure variables in progress.", fileName); + return testingService.createUploadTestConfigResponse(file, fileName); + } + + @PostMapping(value = "/run") + @ResponseStatus(HttpStatus.OK) + public String runTests() { + return "ok"; + } +} diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java new file mode 100644 index 000000000..e5d5c8630 --- /dev/null +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java @@ -0,0 +1,15 @@ +package eu.melodic.upperware.guibackend.controller.testing; + +import lombok.*; +import org.springframework.http.HttpStatus; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UploadTestConfigResponse { + private String modelName; + private HttpStatus httpStatus; + private String message; +} diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java new file mode 100644 index 000000000..f5a9cc47c --- /dev/null +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java @@ -0,0 +1,66 @@ +package eu.melodic.upperware.guibackend.service.testing; + +import eu.melodic.upperware.guibackend.controller.testing.UploadTestConfigResponse; +import eu.passage.upperware.commons.MelodicConstants; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.net4j.connector.ConnectorException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; + +import java.io.File; +import java.io.IOException; + + +@Service +@Slf4j +@AllArgsConstructor(onConstructor = @__(@Autowired)) +public class TestingService { + private final static String CONFIG_FILE_PATH = MelodicConstants.TEST_CONFIG_FILE_DIR + "/tests.yml"; + + public String uploadTestConfig(MultipartFile uploadFileRequest) { + try { + if (uploadFileRequest.getOriginalFilename() == null) { + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + String.format("Problem by uploading your %s file. Please try again.", + uploadFileRequest.getResource().getFilename() + )); + } + + uploadFileRequest.transferTo(new File(CONFIG_FILE_PATH)); + log.info( + "File {} will be stored under path: {}", + uploadFileRequest.getResource().getFilename(), + CONFIG_FILE_PATH + ); + return CONFIG_FILE_PATH; + + } catch (IOException | IllegalStateException e) { + log.error("Error by uploading test configuration file:", e); + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + String.format("Problem by uploading your %s file. Please try again.", + uploadFileRequest.getResource().getFilename() + )); + } catch (ConnectorException e) { + log.error("Error by uploading test configuration file:", e); + throw new ResponseStatusException( + HttpStatus.BAD_REQUEST, + String.format("Problem by uploading your %s file. CDO repository not working. Please try again.", + uploadFileRequest.getResource().getFilename() + )); + } + } + + public UploadTestConfigResponse createUploadTestConfigResponse(MultipartFile file, String fileName) { + return new UploadTestConfigResponse( + fileName, + HttpStatus.OK, + null + ); + } +} 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 e17de0af9..8ff1e2319 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() {} } -- GitLab From 5c77598603e0a8fc0c4116b52a2daedf3e397317 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Wed, 17 Jun 2020 19:37:45 +0200 Subject: [PATCH 12/18] ftt: add gui-backend API methods to upload test config file and run tests --- .../FunctionizerTestingToolController.java | 3 +- .../service/TestRunner.java | 1 - .../test/ServerlessFunctionTestFactory.java | 11 ++++++ .../service/yaml/TestConfigurationLoader.java | 7 ++++ .../communication/commons/ServiceName.java | 3 +- .../FunctionizerTestingToolApi.java | 5 +++ .../FunctionizerTestingToolClientApi.java | 38 +++++++++++++++++++ .../controller/testing/TestingController.java | 6 ++- .../properties/GuiBackendProperties.java | 4 ++ .../service/testing/TestingService.java | 7 +++- 10 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolApi.java create mode 100644 gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolClientApi.java 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 index 0aadc8cb2..24afed031 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java +++ b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/FunctionizerTestingToolController.java @@ -1,9 +1,8 @@ package eu.functionizer.functionizertestingtool; -import eu.functionizer.functionizertestingtool.model.FunctionTestResult; + import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult; import eu.functionizer.functionizertestingtool.service.TestRunner; -import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; 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 index bea876ac6..89a62839c 100644 --- 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 @@ -1,6 +1,5 @@ package eu.functionizer.functionizertestingtool.service; -import eu.functionizer.functionizertestingtool.model.FunctionTestResult; import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult; import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData; import eu.functionizer.functionizertestingtool.service.test.FunctionizerTestListener; 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 index b9be9cd9a..20e22335c 100644 --- 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 @@ -191,6 +191,14 @@ public class ServerlessFunctionTestFactory { 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"); @@ -247,6 +255,9 @@ public class ServerlessFunctionTestFactory { 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(); 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 index 622e984c8..d2045bd21 100644 --- 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 @@ -11,6 +11,7 @@ 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 java.util.HashSet; @@ -45,6 +46,12 @@ public class TestConfigurationLoader { ); 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); } checkTestConfiguration(configuration); return configuration; 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 3b182c4e5..2c443f6a6 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/testingtool/FunctionizerTestingToolApi.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolApi.java new file mode 100644 index 000000000..3c6dbfdb2 --- /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(); +} 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 000000000..f0b7c5bea --- /dev/null +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/communication/testingtool/FunctionizerTestingToolClientApi.java @@ -0,0 +1,38 @@ +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.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 requestUrl = guiBackendProperties.getFunctionizerTestingTool().getUrl() + "/test"; + ParameterizedTypeReference responseType = + new ParameterizedTypeReference() { + }; + ResponseEntity response = getResponse( + requestUrl, + responseType, + null, + ServiceName.FUNCTIONIZER_TESTING_TOOL.name, + HttpMethod.POST + ); + return response.getBody(); + } +} diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java index bbb0ab87b..17bfcc0d6 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java @@ -1,5 +1,6 @@ package eu.melodic.upperware.guibackend.controller.testing; +import eu.melodic.upperware.guibackend.communication.testingtool.FunctionizerTestingToolApi; import eu.melodic.upperware.guibackend.service.testing.TestingService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,6 +15,7 @@ import org.springframework.web.multipart.MultipartFile; public class TestingController { TestingService testingService; + FunctionizerTestingToolApi functionizerTestApi; @PostMapping(value = "/upload", consumes = {"multipart/form-data"}) @ResponseStatus(HttpStatus.CREATED) @@ -29,7 +31,7 @@ public class TestingController { @PostMapping(value = "/run") @ResponseStatus(HttpStatus.OK) - public String runTests() { - return "ok"; + public Object runTests() { + return functionizerTestApi.runTests(); } } diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/properties/GuiBackendProperties.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/properties/GuiBackendProperties.java index a127412cf..fc65991e8 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/properties/GuiBackendProperties.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/properties/GuiBackendProperties.java @@ -43,6 +43,10 @@ public class GuiBackendProperties { @NotNull private ExternalService metaSolver; + @Valid + @NotNull + private ExternalService functionizerTestingTool; + @Getter @Setter public static class Mule extends ExternalService { diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java index f5a9cc47c..491288c7e 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java @@ -30,8 +30,11 @@ public class TestingService { uploadFileRequest.getResource().getFilename() )); } - - uploadFileRequest.transferTo(new File(CONFIG_FILE_PATH)); + File newFile = new File(CONFIG_FILE_PATH); + if (!newFile.exists()) { + newFile.getParentFile().mkdirs(); + } + uploadFileRequest.transferTo(newFile); log.info( "File {} will be stored under path: {}", uploadFileRequest.getResource().getFilename(), -- GitLab From f298c92bac3cf7dd8a4839987100f14a71d7f4f2 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Thu, 18 Jun 2020 14:17:04 +0200 Subject: [PATCH 13/18] [gui-backend] fixed authorization issues --- .../testingtool/FunctionizerTestingToolApi.java | 2 +- .../testingtool/FunctionizerTestingToolClientApi.java | 6 ++++-- .../controller/testing/TestingController.java | 10 ++++++---- .../controller/testing/UploadTestConfigResponse.java | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) 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 index 3c6dbfdb2..d2bd68cff 100644 --- 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 @@ -1,5 +1,5 @@ package eu.melodic.upperware.guibackend.communication.testingtool; public interface FunctionizerTestingToolApi { - Object runTests(); + 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 index f0b7c5bea..8d04ce47e 100644 --- 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 @@ -4,6 +4,7 @@ import eu.melodic.upperware.guibackend.communication.commons.RestCommunicationSe 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; @@ -21,15 +22,16 @@ public class FunctionizerTestingToolClientApi extends RestCommunicationService i } @Override - public Object runTests() { + public Object runTests(String token) { String requestUrl = guiBackendProperties.getFunctionizerTestingTool().getUrl() + "/test"; ParameterizedTypeReference responseType = new ParameterizedTypeReference() { }; + HttpEntity request = createEmptyHttpEntityWithAuthorizationHeader(token); ResponseEntity response = getResponse( requestUrl, responseType, - null, + request, ServiceName.FUNCTIONIZER_TESTING_TOOL.name, HttpMethod.POST ); diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java index 17bfcc0d6..3ffbe6b01 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java @@ -4,6 +4,7 @@ import eu.melodic.upperware.guibackend.communication.testingtool.FunctionizerTes import eu.melodic.upperware.guibackend.service.testing.TestingService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -20,18 +21,19 @@ public class TestingController { @PostMapping(value = "/upload", consumes = {"multipart/form-data"}) @ResponseStatus(HttpStatus.CREATED) public UploadTestConfigResponse uploadTestConfigFile(@RequestParam("file") MultipartFile file) { + String originalName = file.getResource().getFilename(); log.info( "POST request for upload test configuration file with name: {}", - file.getResource().getFilename() + originalName ); String fileName = testingService.uploadTestConfig(file); - log.info("File {} successfully uploaded. Finding secure variables in progress.", fileName); + log.info("File {} successfully uploaded.", originalName); return testingService.createUploadTestConfigResponse(file, fileName); } @PostMapping(value = "/run") @ResponseStatus(HttpStatus.OK) - public Object runTests() { - return functionizerTestApi.runTests(); + public Object runTests(@RequestHeader(value = HttpHeaders.AUTHORIZATION) String token) { + return functionizerTestApi.runTests(token); } } diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java index e5d5c8630..d892f3e01 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java @@ -9,7 +9,7 @@ import org.springframework.http.HttpStatus; @NoArgsConstructor @AllArgsConstructor public class UploadTestConfigResponse { - private String modelName; + private String testConfigFilePath; private HttpStatus httpStatus; private String message; } -- GitLab From 10d139e2372b2a294f22720681f8908075fff691 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Wed, 24 Jun 2020 15:23:37 +0200 Subject: [PATCH 14/18] ftt: add validation on yml upload --- .../test/ServerlessFunctionTestFactory.java | 4 +- .../service/yaml/TestConfigurationLoader.java | 8 +- .../service/testing/TestingService.java | 88 ++++++++++++++++++- .../testing}/FunctionTestConfiguration.java | 2 +- .../commons/model/testing}/TestCase.java | 2 +- .../model/testing}/TestConfiguration.java | 2 +- 6 files changed, 95 insertions(+), 11 deletions(-) rename {functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model => melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing}/FunctionTestConfiguration.java (78%) rename {functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model => melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing}/TestCase.java (76%) rename {functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model => melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing}/TestConfiguration.java (71%) 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 index 20e22335c..33a1c027f 100644 --- 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 @@ -15,9 +15,9 @@ import com.amazonaws.services.lambda.model.InvokeResult; import com.microsoft.azure.management.Azure; import eu.functionizer.functionizertestingtool.model.CloudiatorData; -import eu.functionizer.functionizertestingtool.model.FunctionTestConfiguration; +import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration; import eu.functionizer.functionizertestingtool.model.ReportEntryKey; -import eu.functionizer.functionizertestingtool.model.TestCase; +import eu.passage.upperware.commons.model.testing.TestCase; import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoader; 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 index d2045bd21..d7cd899df 100644 --- 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 @@ -1,8 +1,8 @@ package eu.functionizer.functionizertestingtool.service.yaml; -import eu.functionizer.functionizertestingtool.model.FunctionTestConfiguration; -import eu.functionizer.functionizertestingtool.model.TestCase; -import eu.functionizer.functionizertestingtool.model.TestConfiguration; +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 eu.passage.upperware.commons.MelodicConstants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; @@ -34,7 +34,7 @@ public class TestConfigurationLoader { } catch (FileNotFoundException e) { String errorMessage = String.format( - "File test configuration: %s is missing.", + "Test configuration file: %s is missing.", CONFIG_FILE_PATH ); log.error(errorMessage, e); diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java index 491288c7e..44dc3f71d 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java @@ -2,17 +2,27 @@ package eu.melodic.upperware.guibackend.service.testing; import eu.melodic.upperware.guibackend.controller.testing.UploadTestConfigResponse; import eu.passage.upperware.commons.MelodicConstants; +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.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.eclipse.net4j.connector.ConnectorException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; 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.File; -import java.io.IOException; +import java.io.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @Service @@ -34,6 +44,8 @@ public class TestingService { if (!newFile.exists()) { newFile.getParentFile().mkdirs(); } + validate(uploadFileRequest.getInputStream()); + uploadFileRequest.transferTo(newFile); log.info( "File {} will be stored under path: {}", @@ -66,4 +78,76 @@ public class TestingService { null ); } + + private void validate(InputStream ymlFileInputStream) throws ResponseStatusException { + Yaml yaml = new Yaml(new Constructor(TestConfiguration.class)); + TestConfiguration configuration; + try { + configuration = yaml.load(ymlFileInputStream); + 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() + ); + } + } catch (ConstructorException e) { + String errorMessage = "The file has a bad format and could not be parsed."; + log.error(errorMessage); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); + } + } + + private static void checkFunctionNamesUniqueness( + List functionTestConfigurations + ) throws ResponseStatusException { + 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 ResponseStatusException(HttpStatus.BAD_REQUEST, String.format( + "Function name '%s' occurred in more than one test. Please adjust the config file.", + name + )); + } + } + } + + private static void checkTestCasesUniqueness( + List testCases, String functionName + ) throws ResponseStatusException { + 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 ResponseStatusException( + HttpStatus.BAD_REQUEST, + 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.", + eventExpectedOutput.getKey(), + eventExpectedOutput.getValue(), + functionName + ) + ); + } + } + } } diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestConfiguration.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/FunctionTestConfiguration.java similarity index 78% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestConfiguration.java rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/FunctionTestConfiguration.java index ca5aa28e8..7ffa58719 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/FunctionTestConfiguration.java +++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/FunctionTestConfiguration.java @@ -1,4 +1,4 @@ -package eu.functionizer.functionizertestingtool.model; +package eu.passage.upperware.commons.model.testing; import lombok.Data; diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCase.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestCase.java similarity index 76% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCase.java rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestCase.java index 7bce6ba8d..29b152555 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestCase.java +++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestCase.java @@ -1,4 +1,4 @@ -package eu.functionizer.functionizertestingtool.model; +package eu.passage.upperware.commons.model.testing; import lombok.Data; diff --git a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestConfiguration.java similarity index 71% rename from functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java rename to melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestConfiguration.java index 1610c77a8..4779c12a2 100644 --- a/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/model/TestConfiguration.java +++ b/melodic-commons/src/main/java/eu/passage/upperware/commons/model/testing/TestConfiguration.java @@ -1,4 +1,4 @@ -package eu.functionizer.functionizertestingtool.model; +package eu.passage.upperware.commons.model.testing; import lombok.Data; -- GitLab From d9387023c0860b100da253e5db4a43405214a5ac Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Tue, 30 Jun 2020 11:17:06 +0200 Subject: [PATCH 15/18] ftt: ignore the test cases if provider credentials are null --- .../service/provider/AzureFunctionsService.java | 9 ++------- .../service/test/ServerlessFunctionTestFactory.java | 11 +++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) 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 index ca208ecd7..18a9a8bbd 100644 --- 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 @@ -28,7 +28,7 @@ public class AzureFunctionsService { return stackId + "group"; } - public static Azure buildAzureClient(String user, String secret) throws Exception { + public 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); @@ -40,12 +40,7 @@ public class AzureFunctionsService { AzureEnvironment.AZURE ); - try { - return Azure.authenticate(credentials).withDefaultSubscription(); - } catch (IOException e) { - e.printStackTrace(); - throw new Exception(":("); - } + return Azure.authenticate(credentials).withDefaultSubscription(); } public static String getFunctionKey(Azure azureClient, Function function) { 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 index 33a1c027f..95a4a5248 100644 --- 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 @@ -407,6 +407,15 @@ public class ServerlessFunctionTestFactory { .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() ); @@ -437,6 +446,7 @@ public class ServerlessFunctionTestFactory { stage, e.getMessage() ); + ignoreTestCases(functionNode, functionTestConfiguration); break; } @@ -470,6 +480,7 @@ public class ServerlessFunctionTestFactory { stage, e.getMessage() ); + ignoreTestCases(functionNode, functionTestConfiguration); break; } -- GitLab From 775a1c5e117ee6d6ff4f645e945f7694dfe3f73f Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Wed, 1 Jul 2020 13:40:37 +0200 Subject: [PATCH 16/18] ftt: minor fixes --- functionizer-testing-tool/.gitignore | 31 -- .../.mvn/wrapper/MavenWrapperDownloader.java | 117 ------- .../.mvn/wrapper/maven-wrapper.jar | Bin 50710 -> 0 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 - functionizer-testing-tool/mvnw | 310 ------------------ functionizer-testing-tool/mvnw.cmd | 182 ---------- .../test/ServerlessFunctionTestFactory.java | 2 +- .../guibackend/ApplicationContext.java | 1 - .../controller/testing/TestingController.java | 2 +- .../testing/UploadTestConfigResponse.java | 1 - .../service/testing/TestingService.java | 5 +- 11 files changed, 4 insertions(+), 649 deletions(-) delete mode 100644 functionizer-testing-tool/.gitignore delete mode 100644 functionizer-testing-tool/.mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 functionizer-testing-tool/.mvn/wrapper/maven-wrapper.jar delete mode 100644 functionizer-testing-tool/.mvn/wrapper/maven-wrapper.properties delete mode 100755 functionizer-testing-tool/mvnw delete mode 100644 functionizer-testing-tool/mvnw.cmd diff --git a/functionizer-testing-tool/.gitignore b/functionizer-testing-tool/.gitignore deleted file mode 100644 index a2a3040aa..000000000 --- a/functionizer-testing-tool/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ - -### VS Code ### -.vscode/ diff --git a/functionizer-testing-tool/.mvn/wrapper/MavenWrapperDownloader.java b/functionizer-testing-tool/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index e76d1f324..000000000 --- a/functionizer-testing-tool/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.6"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.jar b/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50710 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 EfAhh=DF6Tf diff --git a/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.properties b/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 642d572ce..000000000 --- a/functionizer-testing-tool/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/functionizer-testing-tool/mvnw b/functionizer-testing-tool/mvnw deleted file mode 100755 index a16b5431b..000000000 --- a/functionizer-testing-tool/mvnw +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/functionizer-testing-tool/mvnw.cmd b/functionizer-testing-tool/mvnw.cmd deleted file mode 100644 index c8d43372c..000000000 --- a/functionizer-testing-tool/mvnw.cmd +++ /dev/null @@ -1,182 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% 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 index 95a4a5248..b4f70f9cd 100644 --- 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 @@ -68,7 +68,7 @@ public class ServerlessFunctionTestFactory { AWS_INVOKE("INVOKE AWS LAMBDA FUNCTION"), END("COLLECTING TESTS FINISH"); - private String name; + private final String name; Stage(String name) { this.name = name; 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 b4538bb20..7b77406c2 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 @@ -14,7 +14,6 @@ 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 io.github.cloudiator.rest.model.Cloud; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.SimpleClientHttpRequestFactory; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java index 3ffbe6b01..ba09838b9 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java @@ -28,7 +28,7 @@ public class TestingController { ); String fileName = testingService.uploadTestConfig(file); log.info("File {} successfully uploaded.", originalName); - return testingService.createUploadTestConfigResponse(file, fileName); + return testingService.createUploadTestConfigResponse(fileName); } @PostMapping(value = "/run") diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java index d892f3e01..005e8777a 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java @@ -11,5 +11,4 @@ import org.springframework.http.HttpStatus; public class UploadTestConfigResponse { private String testConfigFilePath; private HttpStatus httpStatus; - private String message; } diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java index 44dc3f71d..c85ebf8c5 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java @@ -71,11 +71,10 @@ public class TestingService { } } - public UploadTestConfigResponse createUploadTestConfigResponse(MultipartFile file, String fileName) { + public UploadTestConfigResponse createUploadTestConfigResponse(String fileName) { return new UploadTestConfigResponse( fileName, - HttpStatus.OK, - null + HttpStatus.OK ); } -- GitLab From 17326147663ff989889b1d4b1f59ca42d45f8ff6 Mon Sep 17 00:00:00 2001 From: areniewicz Date: Wed, 8 Jul 2020 13:22:50 +0200 Subject: [PATCH 17/18] gitlab CI config for functionizer testing tool --- .gitlab-ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b6bb91bf..98109fce6 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 -- GitLab From 95fa78bd3649955ca2e14b905aff96c3c191eae0 Mon Sep 17 00:00:00 2001 From: mczaplicka Date: Wed, 8 Jul 2020 19:24:36 +0200 Subject: [PATCH 18/18] ftt: moved test config validation to commons and serverless factory decoupling --- .../service/provider/AWSLambdaService.java | 129 +++++++- .../provider/AzureFunctionsService.java | 135 +++++++- .../provider/TestPreparationService.java | 69 ++++ .../test/FunctionizerTestListener.java | 3 - .../test/ServerlessFunctionTestFactory.java | 298 ++---------------- .../service/test/Stage.java | 25 ++ .../service/yaml/TestConfigurationLoader.java | 69 +--- gui-backend/pom.xml | 7 - .../controller/testing/TestingController.java | 1 + .../UploadTestConfigResponse.java | 4 +- .../service/testing/TestingService.java | 74 +---- .../TestConfigurationValidationService.java | 113 +++++++ 12 files changed, 504 insertions(+), 423 deletions(-) create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/provider/TestPreparationService.java create mode 100644 functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/Stage.java rename gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/{ => response}/UploadTestConfigResponse.java (54%) create mode 100644 melodic-commons/src/main/java/eu/passage/upperware/commons/service/testing/TestConfigurationValidationService.java 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 index c17ddb2b6..9b2d7aa30 100644 --- 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 @@ -5,8 +5,28 @@ 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; -public class AWSLambdaService { +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); @@ -16,4 +36,111 @@ public class AWSLambdaService { .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 index 18a9a8bbd..34453b198 100644 --- 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 @@ -4,7 +4,16 @@ 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; @@ -14,13 +23,20 @@ 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; -public class AzureFunctionsService { +@Slf4j +public class AzureFunctionsService extends TestPreparationService { private final static String ENDPOINT_STATIC = "azurewebsites.net/api"; private final static String CLIENT_TENANT_DELIMITER = ":"; - public static String buildFunctionUrl(String appName, String functionName) { + private static String buildFunctionUrl(String appName, String functionName) { return String.format("http://%s.%s/%s", appName, ENDPOINT_STATIC, functionName); } @@ -28,7 +44,7 @@ public class AzureFunctionsService { return stackId + "group"; } - public static Azure buildAzureClient(String user, String secret) throws IOException { + 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); @@ -43,18 +59,18 @@ public class AzureFunctionsService { return Azure.authenticate(credentials).withDefaultSubscription(); } - public static String getFunctionKey(Azure azureClient, Function function) { - String resourceGroup = buildResourceGroupName(function.getStackId()); + private static String getFunctionKey(Azure azureClient, String stackId) { + String resourceGroup = buildResourceGroupName(stackId); FunctionApp functionApp = azureClient .appServices() .functionApps() - .getByResourceGroup(resourceGroup, function.getStackId()); + .getByResourceGroup(resourceGroup, stackId); return functionApp.getMasterKey(); } - public static String invokeFunction(String url, String functionKey, String input) throws Exception { + private static String invokeFunction(String url, String functionKey, String input) throws Exception { RestTemplate restTemplate = new RestTemplate(); @@ -76,4 +92,109 @@ public class AzureFunctionsService { } } + 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 000000000..6e7e26967 --- /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/FunctionizerTestListener.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/FunctionizerTestListener.java index d6a66f5c5..a93bec0a5 100644 --- 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 @@ -8,9 +8,6 @@ import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import java.time.Clock; -import java.util.stream.Stream; - -import static java.util.stream.Stream.concat; public class FunctionizerTestListener implements TestExecutionListener { 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 index b4f70f9cd..a84df2e83 100644 --- 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 @@ -2,24 +2,15 @@ package eu.functionizer.functionizertestingtool.service.test; import java.io.FileInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.amazonaws.services.lambda.AWSLambda; -import com.amazonaws.services.lambda.model.InvokeRequest; -import com.amazonaws.services.lambda.model.InvokeResult; - -import com.microsoft.azure.management.Azure; - 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.passage.upperware.commons.model.testing.TestCase; -import eu.functionizer.functionizertestingtool.service.provider.AWSLambdaService; -import eu.functionizer.functionizertestingtool.service.provider.AzureFunctionsService; import eu.functionizer.functionizertestingtool.service.yaml.TestConfigurationLoader; import eu.passage.upperware.commons.cloudiator.CloudiatorApi; import eu.passage.upperware.commons.cloudiator.CloudiatorClientApi; @@ -43,9 +34,9 @@ import io.github.cloudiator.rest.model.*; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; +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 { @@ -55,30 +46,6 @@ public class ServerlessFunctionTestFactory { public static final String ALL_TESTS_DISPLAY_NAME = "All tests"; - private 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; - } - } - private static Map providerMap; private static CloudiatorApi cloudiatorApi; @@ -142,25 +109,13 @@ public class ServerlessFunctionTestFactory { this.testReporter = testReporter; } - private 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); - - } @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"); @@ -217,7 +172,11 @@ public class ServerlessFunctionTestFactory { if (function == null) { functionNode = new ArrayList<>(); - failDynamicNode(functionConfigurationName, stage, "Not found in the deployed Function list"); + failDynamicNode( + functionConfigurationName, + stage, + "Not found in the deployed Function list" + ); ignoreTestCases(functionNode, functionTestConfiguration); } else { @@ -375,12 +334,6 @@ public class ServerlessFunctionTestFactory { .collect(Collectors.toMap(Credential::getUser, Credential::getSecret)); } - private Map createReportEntry(String displayName) { - Map reportEntry = new HashMap<>(); - reportEntry.put("displayName", displayName); - return reportEntry; - } - private List prepareFunctionNode( FunctionTestConfiguration functionTestConfiguration, NodeCandidate nodeCandidate, @@ -422,87 +375,27 @@ public class ServerlessFunctionTestFactory { switch (provider) { case AWS_EC2: - AWSLambda awsLambdaClient; + String location = Objects.requireNonNull(nodeCandidate.getLocation()).getProviderId(); - log.debug("Building AWS Lambda Function name"); - String awsFunctionName = String.join( - "-", + prepareAWSLambdaTests( + functionNode, + functionTestConfiguration, function.getStackId(), - functionDeployedName + functionDeployedName, + user, + secret, + location ); - - log.debug("Building AWS Lambda client"); - stage = Stage.BUILD_AWS_LAMBDA_CLIENT; - try { - awsLambdaClient = AWSLambdaService.buildClient( - user, - secret, - Objects.requireNonNull(nodeCandidate.getLocation()) - .getProviderId() - ); - } catch (Exception e) { - failDynamicNode - (functionTestConfiguration.getFunctionName(), - stage, - e.getMessage() - ); - ignoreTestCases(functionNode, functionTestConfiguration); - break; - } - - List awsTests = functionTestConfiguration.getTestCases() - .stream() - .map(testCase -> createAWSLambdaTest( - awsLambdaClient, - functionTestConfiguration.getFunctionName(), - awsFunctionName, - testCase - )) - .collect(Collectors.toList()); - functionNode.addAll(awsTests); break; case AZURE: - Azure azureClient; - String functionKey; - - try { - log.debug("Building Azure client"); - stage = Stage.BUILD_AZURE_CLIENT; - azureClient = AzureFunctionsService.buildAzureClient(user, secret); - - log.debug("Fetching Function Key"); - stage = Stage.GET_AZURE_FUNCTION_KEY; - functionKey = AzureFunctionsService.getFunctionKey(azureClient, function); - } catch (Exception e) { - failDynamicNode - (functionTestConfiguration.getFunctionName(), - stage, - e.getMessage() - ); - ignoreTestCases(functionNode, functionTestConfiguration); - break; - } - - log.debug("Building Function URL"); - String azureFunctionEndpoint = AzureFunctionsService.buildFunctionUrl( + prepareAzureTests( + functionNode, + functionTestConfiguration, function.getStackId(), - functionTestConfiguration.getTriggerPath() + user, + secret ); - log.info("Function URL = {}", azureFunctionEndpoint); - - List azureTests = functionTestConfiguration - .getTestCases() - .stream() - .map(testCase -> createAzureTest( - functionTestConfiguration.getFunctionName(), - azureFunctionEndpoint, - functionKey, - testCase - )) - .collect(Collectors.toList()); - - functionNode.addAll(azureTests); break; default: @@ -514,149 +407,4 @@ public class ServerlessFunctionTestFactory { } return functionNode; } - - private static String createTestCaseDisplayName( - String functionName, - String input, - String expectedOutput - ) { - return String.format( - "Function %s invoked with %s should return %s", - functionName, - input, - expectedOutput - ); - } - - private 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() - ) - ); - } - - private DynamicTest createAzureTest( - String functionName, - String azureFunctionEndpoint, - String functionKey, - 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, - () -> executeAzureTestCase( - azureFunctionEndpoint, - functionKey, - testCase.getEvent(), - testCase.getExpectedOutput() - ) - ); - } - - public void executeAWSLambdaTestCase( - AWSLambda awsLambda, - String functionName, - String event, - String expectedOutput - ) { - stage = Stage.TEST_EXECUTION; - - InvokeRequest invokeRequest = new InvokeRequest() - .withFunctionName(functionName) - .withPayload(event); - - try { - stage = Stage.AWS_INVOKE; - 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() - )); - } - - } - - public void executeAzureTestCase( - String endpoint, - String functionKey, - String event, - String expectedOutput - ) throws Exception { - stage = Stage.TEST_EXECUTION; - String response = AzureFunctionsService.invokeFunction( - endpoint, - functionKey, - event - ); - assertEquals(expectedOutput, response); - } - - private 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/Stage.java b/functionizer-testing-tool/src/main/java/eu/functionizer/functionizertestingtool/service/test/Stage.java new file mode 100644 index 000000000..25a12ea98 --- /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 index d7cd899df..fa6bfda4f 100644 --- 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 @@ -1,11 +1,9 @@ package eu.functionizer.functionizertestingtool.service.yaml; -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 eu.passage.upperware.commons.MelodicConstants; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @@ -14,10 +12,8 @@ import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.constructor.ConstructorException; import java.io.*; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; + +import static eu.passage.upperware.commons.service.testing.TestConfigurationValidationService.validate; @Service @Slf4j @@ -53,67 +49,10 @@ public class TestConfigurationLoader { log.error(errorMessage); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); } - checkTestConfiguration(configuration); + validate(configuration); return configuration; } - private static void checkTestConfiguration(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() - ); - } - } - - private static void checkFunctionNamesUniqueness( - List functionTestConfigurations - ) throws Exception { - 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 Exception(String.format( - "Function name '%s' occurred in more than one test. Please adjust the config file", - name - )); - } - } - } - - private static void checkTestCasesUniqueness(List testCases, String functionName) throws Exception { - 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 Exception(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", - eventExpectedOutput.getKey(), - eventExpectedOutput.getValue(), - functionName - )); - } - } - } private void saveTestConfiguration(TestConfiguration testConfiguration) { Yaml yaml = new Yaml(); diff --git a/gui-backend/pom.xml b/gui-backend/pom.xml index 4f9b10fb4..ff85b6922 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/controller/testing/TestingController.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java index ba09838b9..db47da5f2 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/TestingController.java @@ -1,6 +1,7 @@ package eu.melodic.upperware.guibackend.controller.testing; import eu.melodic.upperware.guibackend.communication.testingtool.FunctionizerTestingToolApi; +import eu.melodic.upperware.guibackend.controller.testing.response.UploadTestConfigResponse; import eu.melodic.upperware.guibackend.service.testing.TestingService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/response/UploadTestConfigResponse.java similarity index 54% rename from gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java rename to gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/response/UploadTestConfigResponse.java index 005e8777a..d16aa9cf1 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/UploadTestConfigResponse.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/controller/testing/response/UploadTestConfigResponse.java @@ -1,7 +1,6 @@ -package eu.melodic.upperware.guibackend.controller.testing; +package eu.melodic.upperware.guibackend.controller.testing.response; import lombok.*; -import org.springframework.http.HttpStatus; @Getter @Setter @@ -10,5 +9,4 @@ import org.springframework.http.HttpStatus; @AllArgsConstructor public class UploadTestConfigResponse { private String testConfigFilePath; - private HttpStatus httpStatus; } diff --git a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java index c85ebf8c5..aa3e7a715 100644 --- a/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java +++ b/gui-backend/src/main/java/eu/melodic/upperware/guibackend/service/testing/TestingService.java @@ -1,13 +1,12 @@ package eu.melodic.upperware.guibackend.service.testing; -import eu.melodic.upperware.guibackend.controller.testing.UploadTestConfigResponse; +import eu.melodic.upperware.guibackend.controller.testing.response.UploadTestConfigResponse; import eu.passage.upperware.commons.MelodicConstants; 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 eu.passage.upperware.commons.service.testing.TestConfigurationValidationService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.Pair; import org.eclipse.net4j.connector.ConnectorException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -19,10 +18,9 @@ import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.constructor.ConstructorException; import java.io.*; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; + +import static eu.passage.upperware.commons.service.testing.TestConfigurationValidationService.checkFunctionNamesUniqueness; +import static eu.passage.upperware.commons.service.testing.TestConfigurationValidationService.checkTestCasesUniqueness; @Service @@ -72,10 +70,7 @@ public class TestingService { } public UploadTestConfigResponse createUploadTestConfigResponse(String fileName) { - return new UploadTestConfigResponse( - fileName, - HttpStatus.OK - ); + return new UploadTestConfigResponse(fileName); } private void validate(InputStream ymlFileInputStream) throws ResponseStatusException { @@ -87,7 +82,7 @@ public class TestingService { checkFunctionNamesUniqueness(configuration.getTests()); log.info("Checking uniqueness of test cases"); - for (FunctionTestConfiguration functionTestConfiguration : configuration.getTests()){ + for (FunctionTestConfiguration functionTestConfiguration : configuration.getTests()) { checkTestCasesUniqueness( functionTestConfiguration.getTestCases(), functionTestConfiguration.getFunctionName() @@ -97,56 +92,11 @@ public class TestingService { String errorMessage = "The file has a bad format and could not be parsed."; log.error(errorMessage); throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); - } - } - - private static void checkFunctionNamesUniqueness( - List functionTestConfigurations - ) throws ResponseStatusException { - 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 ResponseStatusException(HttpStatus.BAD_REQUEST, String.format( - "Function name '%s' occurred in more than one test. Please adjust the config file.", - name - )); - } - } - } - - private static void checkTestCasesUniqueness( - List testCases, String functionName - ) throws ResponseStatusException { - 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 ResponseStatusException( - HttpStatus.BAD_REQUEST, - 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.", - eventExpectedOutput.getKey(), - eventExpectedOutput.getValue(), - functionName - ) - ); - } + } catch ( + TestConfigurationValidationService.NotUniqueFunctionNameException + | TestConfigurationValidationService.NotUniqueTestCaseException e + ) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); } } } 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 000000000..e0b8bc037 --- /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() + ); + } + } + } +} -- GitLab