diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c7e96a73..43edd1150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ All notable changes to this project will be documented in this file. +## [[8.0.0]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v8.0.0) 2023-03-08 + +### New Features +* Support Gramine framework for TEE tasks. +* Retrieve location of SMS services through an _iExec Platform Registry_. +* Improve authentication on scheduler. + * burn challenge after login. + * handle JWT expiration through the expiration claim. + * cache JWT until expiration. + * better claims usage. +* Show application version on banner. +### Bug Fixes +* Always return a `TaskNotificationType` on replicate status update when it has been authorized. +* Handle task added twice. +### Quality +* Improve code quality and tests. +* Removed unused variables in configuration. +* Use existing `toString()` method to serialize and hash scheduler public configuration. +* Use recommended annotation in `MetricController`. +* Remove `spring-cloud-starter-openfeign` dependency. +### Dependency Upgrades +* Replace the deprecated `openjdk` Docker base image with `eclipse-temurin` and upgrade to Java 11.0.18 patch. +* Upgrade to Spring Boot 2.6.14. +* Upgrade to Gradle 7.6. +* Upgrade OkHttp to 4.9.0. +* Upgrade `jjwt` to `jjwt-api` 0.11.5. +* Upgrade to `iexec-common` 7.0.0. +* Upgrade to `jenkins-library` 2.4.0. + ## [[7.3.1]](https://github.com/iExecBlockchainComputing/iexec-core/releases/tag/v7.3.1) 2023-02-17 * Subscribe only to deal events targeting a specific workerpool. diff --git a/Dockerfile b/Dockerfile index a3e5ec1e1..057bd7430 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:11.0.15-jre-slim +FROM eclipse-temurin:11.0.18_10-jre ARG jar diff --git a/Jenkinsfile b/Jenkinsfile index 89896be81..b0b918a1c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,4 +1,4 @@ -@Library('global-jenkins-library@2.2.3') _ +@Library('global-jenkins-library@2.4.0') _ buildJavaProject( buildInfo: getBuildInfo(), integrationTestsEnvVars: [], diff --git a/README.md b/README.md index 89a77d901..efc103098 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ To run properly, the _iExec Core Scheduler_ requires: * A _MongoDB_ instance to persist its data. * An _iExec Blockchain Adapter_ for several blockchain network interactions. * An _iExec Result Proxy_ to check if tasks results have been published. -* An _iExec Secret Management Service_ (_iExec SMS_) for secret and enclave sessions management of TEE tasks. +* An _iExec Platform Registry_ to retrieve locations of _iExec SMS_ services. +* One or many _iExec Secret Management Service_ instances (referenced by the _iExec Platform Registry_) to handle secrets and enclave sessions of TEE tasks. You can configure the _iExec Core Scheduler_ with the following properties: @@ -23,17 +24,16 @@ You can configure the _iExec Core Scheduler_ with the following properties: | `IEXEC_CORE_PORT` | Server port of the _iExec Core Scheduler_. | Positive integer | `13000` | | `MONGO_HOST` | _MongoDB_ server host. Cannot be set with URI. | String | `localhost` | | `MONGO_PORT` | _MongoDB_ server port. Cannot be set with URI. | Positive integer | `13002` | +| `IEXEC_PLATFORM_REGISTRY` | _iExec Platform Registry_ server URL. | URL | | +| `IEXEC_PLATFORM_REGISTRY_STACK` | [optional] Use a specific stack configuration exposed by the _iExec Platform Registry_. | String | | +| `IEXEC_PLATFORM_REGISTRY_LABEL` | [optional] Use a labeled version of configuration files exposed by the _iExec Platform Registry_. It might be a Git label such as `main`, `v10` or `07998be`. | String | | | `REVEAL_TIMEOUT_PERIOD` | Detector period to track reveal timeouts for tasks. | Positive integer | `120000` | | `IEXEC_ASK_REPLICATE_PERIOD` | Worker configuration, interval in milliseconds between 2 replicate requests. | Positive integer | `5000` | | `IEXEC_CORE_REQUIRED_WORKER_VERSION` | Empty value will allow any worker version. | String | | | `IEXEC_WORKERS_WHITELIST` | List of worker addresses allowed to connect to the _iExec Core Scheduler_. | String | | | `IEXEC_CORE_WALLET_PATH` | Path to the wallet of the server. | String | `./src/main/resources/wallet/encrypted-wallet_scheduler.json` | | `IEXEC_CORE_WALLET_PASSWORD` | Password to unlock the wallet of the server. | String | `whatever` | -| `IEXEC_CHAIN_ID` | Chain ID of the blockchain network to connect. | Positive integer | `17` | -| `IEXEC_IS_SIDECHAIN` | Define if iExec on-chain protocol is built on top of token (`false`) or native currency (`true`). | Boolean | `false` | | `IEXEC_PRIVATE_CHAIN_ADDRESS` | Private URL to connect to the blockchain node. | URL | `http://localhost:8545` | -| `IEXEC_PUBLIC_CHAIN_ADDRESS` | [unused] Public URL to connect to the blockchain node. | URL | `http://localhost:8545` | -| `IEXEC_HUB_ADDRESS` | Proxy contract address to interact with the iExec on-chain protocol. | String | `0xBF6B2B07e47326B7c8bfCb4A5460bef9f0Fd2002` | | `POOL_ADDRESS` | On-chain address of the workerpool managed by the current _iExec Core Scheduler_. | String | `0x365E7BABAa85eC61Dffe5b520763062e6C29dA27` | | `IEXEC_START_BLOCK_NUMBER` | Subscribe to new deal events from a specific block number. | Positive integer | `0` | | `IEXEC_GAS_PRICE_MULTIPLIER` | Transactions will be sent with `networkGasPrice * gasPriceMultiplier`. | Float | `1.0` | @@ -46,11 +46,6 @@ You can configure the _iExec Core Scheduler_ with the following properties: | `IEXEC_RESULT_REPOSITORY_PROTOCOL` | _iExec Result Proxy_ server communication protocol. | String | `http` | | `IEXEC_RESULT_REPOSITORY_HOST` | _iExec Result Proxy_ server host. | String | `localhost` | | `IEXEC_RESULT_REPOSITORY_PORT` | _iExec Result Proxy_ server port. | Positive integer | `13200` | -| `IEXEC_IPFS_HOST` | [unused] _IPFS_ node host. | String | `127.0.0.1` | -| `IEXEC_IPFS_PORT` | [unused] _IPFS_ node port. | Positive integer | `5001` | -| `IEXEC_SMS_PROTOCOL` | _iExec SMS_ server communication protocol. | String | `http` | -| `IEXEC_SMS_HOST` | _iExec SMS_ server host. | String | `localhost` | -| `IEXEC_SMS_PORT` | _iExec SMS_ server port. | Positive integer | `13300` | | `IEXEC_CORE_MANAGEMENT_ACTUATORS` | Endpoint IDs that should be included or `*` for all. | String | `health, info` | | `IEXEC_CORE_GRAYLOG_HOST` | _Graylog_ server host. | String | `localhost` | | `IEXEC_CORE_GRAYLOG_PORT` | _Graylog_ server port. | Positive integer | `12201` | diff --git a/build.gradle b/build.gradle index 1b345c91e..eba2c6fbc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,18 @@ plugins { id 'java' - id 'eclipse' + id 'io.freefair.lombok' version '6.6.1' + id 'org.springframework.boot' version '2.6.14' + id 'io.spring.dependency-management' version '1.1.0' id 'jacoco' id 'org.sonarqube' version '3.3' id 'maven-publish' - id "org.springframework.boot" version "2.6.2" - id "io.spring.dependency-management" version "1.0.11.RELEASE" } group = 'com.iexec.core' -sourceCompatibility = 11 -targetCompatibility = 11 ext { - springCloudVersion = '2021.0.0' + springCloudVersion = '2021.0.5' + jjwtVersion = '0.11.5' mongockVersion = '4.2.7.BETA' } @@ -38,7 +37,6 @@ repositories { maven { url "https://nexus.intra.iex.ec/repository/maven-public/" } - maven { url "https://jitpack.io" } } @@ -64,23 +62,27 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-data-mongodb" implementation "org.springframework.boot:spring-boot-starter-hateoas" implementation "org.springframework.boot:spring-boot-starter-security" + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework.boot:spring-boot-starter-websocket" - implementation "org.springframework.cloud:spring-cloud-starter-openfeign" + implementation 'org.springframework.cloud:spring-cloud-starter-config' implementation "org.springframework.retry:spring-retry" + // apache commons.lang3 + implementation 'org.apache.commons:commons-lang3' + // NoSuchMethodError: 'okhttp3.RequestBody okhttp3.RequestBody.create(java.lang.String, okhttp3.MediaType)' // Spring Boot dependencies BOM enforces okhttp3 3.14.9 in 2.6.X // It is required to define the dependency version required by web3j until migration to at least Spring Boot 2.7.X implementation 'com.squareup.okhttp3:okhttp:4.9.0' // Web3j issue: https://github.com/web3j/web3j/issues/1180 - testImplementation "org.springframework.boot:spring-boot-starter-test" - // Spring Doc implementation 'org.springdoc:springdoc-openapi-ui:1.6.3' // jason web token - implementation "io.jsonwebtoken:jjwt:0.7.0" + implementation "io.jsonwebtoken:jjwt-api:$jjwtVersion" + runtimeOnly "io.jsonwebtoken:jjwt-impl:$jjwtVersion" + runtimeOnly "io.jsonwebtoken:jjwt-jackson:$jjwtVersion" // expiring map implementation "net.jodah:expiringmap:0.5.10" @@ -93,18 +95,14 @@ dependencies { implementation 'io.micrometer:micrometer-registry-prometheus:1.8.1' - // lombok - compileOnly "org.projectlombok:lombok:1.18.2" - annotationProcessor "org.projectlombok:lombok:1.18.2" - testCompileOnly "org.projectlombok:lombok:1.18.2" - testAnnotationProcessor "org.projectlombok:lombok:1.18.2" - // mongock implementation "com.github.cloudyrock.mongock:mongock-spring-v5:${mongockVersion}" implementation "com.github.cloudyrock.mongock:mongodb-springdata-v2-driver:${mongockVersion}" + testImplementation 'org.springframework.boot:spring-boot-starter-test' + // awaitility - testImplementation 'org.awaitility:awaitility:4.0.1' + testImplementation 'org.awaitility:awaitility' // mongo testImplementation 'org.testcontainers:testcontainers:1.16.2' @@ -112,6 +110,12 @@ dependencies { testImplementation 'org.testcontainers:mongodb:1.16.2' } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } +} + jar { enabled = true archiveClassifier.set('library') @@ -121,6 +125,13 @@ springBoot { buildInfo() } +tasks.named("bootJar") { + manifest { + attributes("Implementation-Title": "iExec Core Scheduler", + "Implementation-Version": project.version) + } +} + test { useJUnitPlatform() } @@ -136,7 +147,7 @@ jacoco { // sonarqube code coverage requires jacoco XML report jacocoTestReport { reports { - xml.enabled true + xml.required = true } } tasks.test.finalizedBy tasks.jacocoTestReport @@ -145,7 +156,7 @@ tasks.sonarqube.dependsOn tasks.jacocoTestReport publishing { publications { maven(MavenPublication) { - artifact bootJar + artifact tasks.named("bootJar") from components.java } } diff --git a/gradle.properties b/gradle.properties index e62901e8c..87b240e36 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ -version=7.3.1 -iexecCommonVersion=6.2.0 -iexecBlockchainAdapterVersion=7.3.0 -iexecResultVersion=7.3.0 -iexecSmsVersion=7.3.0 +version=8.0.0 +iexecCommonVersion=7.0.0 +iexecBlockchainAdapterVersion=8.0.0 +iexecResultVersion=8.0.0 +iexecSmsVersion=8.0.0 nexusUser nexusPassword diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c02..943f0cbfa 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d9132e..f398c33c4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c8..65dcd68d6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -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 +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 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" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..6689b85be 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/lombok.config b/lombok.config new file mode 100644 index 000000000..189c0bef9 --- /dev/null +++ b/lombok.config @@ -0,0 +1,3 @@ +# This file is generated by the 'io.freefair.lombok' Gradle plugin +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/src/main/java/com/iexec/core/AppConfig.java b/src/main/java/com/iexec/core/AppConfig.java index 0fe288a44..5c5a32cea 100644 --- a/src/main/java/com/iexec/core/AppConfig.java +++ b/src/main/java/com/iexec/core/AppConfig.java @@ -17,8 +17,6 @@ package com.iexec.core; import com.iexec.core.config.*; - -import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -31,7 +29,5 @@ WebMvcConfig.class, WebSocketConfig.class, }) -@EnableFeignClients public class AppConfig { - -} \ No newline at end of file +} diff --git a/src/main/java/com/iexec/core/chain/ChainConfig.java b/src/main/java/com/iexec/core/chain/ChainConfig.java index 1706d35e6..918f24760 100644 --- a/src/main/java/com/iexec/core/chain/ChainConfig.java +++ b/src/main/java/com/iexec/core/chain/ChainConfig.java @@ -34,12 +34,12 @@ public class ChainConfig { @Value("#{blockchainAdapterService.publicChainConfig.isSidechain()}") private boolean isSidechain; - @Value("${chain.privateAddress}") - private String privateChainAddress; - @Value("#{blockchainAdapterService.publicChainConfig.iexecHubContractAddress}") private String hubAddress; + @Value("${chain.privateAddress}") + private String privateChainAddress; + @Value("${chain.poolAddress}") private String poolAddress; diff --git a/src/main/java/com/iexec/core/chain/adapter/BlockchainAdapterClientConfig.java b/src/main/java/com/iexec/core/chain/adapter/BlockchainAdapterClientConfig.java index 083402b35..3d33e6b17 100644 --- a/src/main/java/com/iexec/core/chain/adapter/BlockchainAdapterClientConfig.java +++ b/src/main/java/com/iexec/core/chain/adapter/BlockchainAdapterClientConfig.java @@ -19,12 +19,10 @@ import com.iexec.blockchain.api.BlockchainAdapterApiClient; import com.iexec.blockchain.api.BlockchainAdapterApiClientBuilder; import feign.Logger; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -@Slf4j @Configuration public class BlockchainAdapterClientConfig { diff --git a/src/main/java/com/iexec/core/configuration/PublicConfigurationService.java b/src/main/java/com/iexec/core/configuration/PublicConfigurationService.java index 4ce837b1a..5fd2238e6 100644 --- a/src/main/java/com/iexec/core/configuration/PublicConfigurationService.java +++ b/src/main/java/com/iexec/core/configuration/PublicConfigurationService.java @@ -20,6 +20,7 @@ import com.iexec.core.chain.ChainConfig; import com.iexec.core.chain.CredentialsService; import com.iexec.core.chain.adapter.BlockchainAdapterClientConfig; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.web3j.crypto.Hash; @@ -29,13 +30,13 @@ * This simple service will generate a random session id when the scheduler is started, it will be send to workers when * they ping the scheduler. If they see that the session id has changed, it means that the scheduler has restarted. */ +@Slf4j @Service public class PublicConfigurationService { private final ChainConfig chainConfig; private final CredentialsService credentialsService; private final WorkerConfiguration workerConfiguration; private final ResultRepositoryConfiguration resultRepoConfig; - private final SmsConfiguration smsConfiguration; private final BlockchainAdapterClientConfig blockchainAdapterClientConfig; private PublicConfiguration publicConfiguration = null; @@ -50,13 +51,11 @@ public PublicConfigurationService(ChainConfig chainConfig, CredentialsService credentialsService, WorkerConfiguration workerConfiguration, ResultRepositoryConfiguration resultRepoConfig, - SmsConfiguration smsConfiguration, BlockchainAdapterClientConfig blockchainAdapterClientConfig) { this.chainConfig = chainConfig; this.credentialsService = credentialsService; this.workerConfiguration = workerConfiguration; this.resultRepoConfig = resultRepoConfig; - this.smsConfiguration = smsConfiguration; this.blockchainAdapterClientConfig = blockchainAdapterClientConfig; } @@ -67,24 +66,12 @@ void buildPublicConfiguration() { .blockchainAdapterUrl(blockchainAdapterClientConfig.getUrl()) .schedulerPublicAddress(credentialsService.getCredentials().getAddress()) .resultRepositoryURL(resultRepoConfig.getResultRepositoryURL()) - .smsURL(smsConfiguration.getSmsURL()) .askForReplicatePeriod(workerConfiguration.getAskForReplicatePeriod()) .requiredWorkerVersion(workerConfiguration.getRequiredWorkerVersion()) .build(); - - // TODO: would be great to put this in Common - // (a simple `@ToString` would be sufficient) - final String publicConfigurationAsString = String.join("\n", - publicConfiguration.getWorkerPoolAddress(), - publicConfiguration.getBlockchainAdapterUrl(), - publicConfiguration.getSchedulerPublicAddress(), - publicConfiguration.getResultRepositoryURL(), - publicConfiguration.getSmsURL(), - publicConfiguration.getAskForReplicatePeriod() + "", - publicConfiguration.getRequiredWorkerVersion() - ); - - this.publicConfigurationHash = Hash.sha3String(publicConfigurationAsString); + this.publicConfigurationHash = Hash.sha3String(publicConfiguration.toString()); + log.info(publicConfiguration.toString()); + log.info("Public configuration hash {}", publicConfigurationHash); } public String getPublicConfigurationHash() { diff --git a/src/main/java/com/iexec/core/configuration/PurgeConfiguration.java b/src/main/java/com/iexec/core/configuration/PurgeConfiguration.java new file mode 100644 index 000000000..cdd322601 --- /dev/null +++ b/src/main/java/com/iexec/core/configuration/PurgeConfiguration.java @@ -0,0 +1,25 @@ +package com.iexec.core.configuration; + +import com.iexec.common.lifecycle.purge.PurgeService; +import com.iexec.common.lifecycle.purge.Purgeable; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class PurgeConfiguration { + /** + * Creates a {@link PurgeService} bean, with a list of all {@link Purgeable} beans as a parameter. + *

+ * If no {@link Purgeable} bean is known, then an empty list is passed as a parameter. + * This is a special case of Spring IoC, please see + * Spring documentation. + * @param purgeableServices List of services that can be purged on a task completion + * @return An instance of {@link PurgeService} containing a list of all {@link Purgeable} beans. + */ + @Bean + PurgeService purgeService(List purgeableServices) { + return new PurgeService(purgeableServices); + } +} diff --git a/src/main/java/com/iexec/core/configuration/SmsConfiguration.java b/src/main/java/com/iexec/core/configuration/SmsConfiguration.java deleted file mode 100644 index b0cb2bd89..000000000 --- a/src/main/java/com/iexec/core/configuration/SmsConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020 IEXEC BLOCKCHAIN TECH - * - * 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 - * - * http://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. - */ - -package com.iexec.core.configuration; - -import com.iexec.sms.api.SmsClient; -import com.iexec.sms.api.SmsClientBuilder; -import feign.Logger; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; - -@Component -@Getter -@AllArgsConstructor -@NoArgsConstructor -public class SmsConfiguration { - - @Value("${sms.protocol}") - private String protocol; - - @Value("${sms.host}") - private String host; - - @Value("${sms.port}") - private String port; - - public String getSmsURL() { - return protocol + "://" + host + ":" + port; - } - - @Bean - public SmsClient smsClient() { - return SmsClientBuilder.getInstance(Logger.Level.NONE, getSmsURL()); - } -} diff --git a/src/main/java/com/iexec/core/contribution/ContributionHelper.java b/src/main/java/com/iexec/core/contribution/ContributionHelper.java index 8b25b6338..90ec12e50 100644 --- a/src/main/java/com/iexec/core/contribution/ContributionHelper.java +++ b/src/main/java/com/iexec/core/contribution/ContributionHelper.java @@ -21,7 +21,6 @@ import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; public class ContributionHelper { @@ -38,12 +37,9 @@ static int getContributedWeight(List replicates, String contribution) int groupWeight = 0; for (Replicate replicate : replicates) { - Optional lastRelevantStatus = replicate.getLastRelevantStatus(); - if (lastRelevantStatus.isEmpty()) { - continue; - } + ReplicateStatus lastRelevantStatus = replicate.getLastRelevantStatus(); - boolean isContributed = lastRelevantStatus.get().equals(ReplicateStatus.CONTRIBUTED); + boolean isContributed = lastRelevantStatus == ReplicateStatus.CONTRIBUTED; boolean haveSameContribution = contribution.equals(replicate.getContributionHash()); boolean hasWeight = replicate.getWorkerWeight() > 0; @@ -64,14 +60,11 @@ static int getPendingWeight(List replicates, long maxExecutionTime) { for (Replicate replicate : replicates) { - Optional lastRelevantStatus = replicate.getLastRelevantStatus(); - if (lastRelevantStatus.isEmpty()) { - continue; - } + ReplicateStatus lastRelevantStatus = replicate.getLastRelevantStatus(); boolean isCreatedLessThanOnePeriodAgo = !replicate.isCreatedMoreThanNPeriodsAgo(1, maxExecutionTime); - boolean isNotContributed = !lastRelevantStatus.get().equals(ReplicateStatus.CONTRIBUTED); - boolean isNotFailed = !lastRelevantStatus.get().equals(ReplicateStatus.FAILED); + boolean isNotContributed = lastRelevantStatus != ReplicateStatus.CONTRIBUTED; + boolean isNotFailed = lastRelevantStatus != ReplicateStatus.FAILED; boolean hasWeight = replicate.getWorkerWeight() > 0; if (isCreatedLessThanOnePeriodAgo && isNotContributed && isNotFailed && hasWeight) { @@ -92,12 +85,8 @@ static Set getDistinctContributions(List replicates) { for (Replicate replicate : replicates) { - Optional lastRelevantStatus = replicate.getLastRelevantStatus(); - if (lastRelevantStatus.isEmpty()) { - continue; - } - - if (lastRelevantStatus.get().equals(ReplicateStatus.CONTRIBUTED)) { + ReplicateStatus lastRelevantStatus = replicate.getLastRelevantStatus(); + if (lastRelevantStatus == ReplicateStatus.CONTRIBUTED) { distinctContributions.add(replicate.getContributionHash()); } } diff --git a/src/main/java/com/iexec/core/detector/replicate/UnnotifiedAbstractDetector.java b/src/main/java/com/iexec/core/detector/replicate/UnnotifiedAbstractDetector.java index 3559e2ac9..47a588c1a 100644 --- a/src/main/java/com/iexec/core/detector/replicate/UnnotifiedAbstractDetector.java +++ b/src/main/java/com/iexec/core/detector/replicate/UnnotifiedAbstractDetector.java @@ -30,7 +30,6 @@ import lombok.extern.slf4j.Slf4j; import java.util.List; -import java.util.Optional; import static com.iexec.common.replicate.ReplicateStatus.WORKER_LOST; import static com.iexec.common.replicate.ReplicateStatus.getMissingStatuses; @@ -60,8 +59,8 @@ void dectectOnchainCompletedWhenOffchainCompleting(List detectWhenOf ChainContributionStatus onchainCompleted) { for (Task task : taskService.findByCurrentStatus(detectWhenOffChainTaskStatuses)) { for (Replicate replicate : replicatesService.getReplicates(task.getChainTaskId())) { - Optional lastRelevantStatus = replicate.getLastRelevantStatus(); - if (lastRelevantStatus.isEmpty() || !lastRelevantStatus.get().equals(offchainCompleting)) { + ReplicateStatus lastRelevantStatus = replicate.getLastRelevantStatus(); + if (lastRelevantStatus != offchainCompleting) { continue; } @@ -69,7 +68,7 @@ void dectectOnchainCompletedWhenOffchainCompleting(List detectWhenOf if (statusTrueOnChain) { log.info("Detected confirmed missing update (replicate) [is:{}, should:{}, taskId:{}]", - lastRelevantStatus.get(), onchainCompleted, task.getChainTaskId()); + lastRelevantStatus, onchainCompleted, task.getChainTaskId()); updateReplicateStatuses(task, replicate, offchainCompleted); } } @@ -82,9 +81,9 @@ void dectectOnchainCompleted(List detectWhenOffChainTaskStatuses, ChainContributionStatus onchainCompleted) { for (Task task : taskService.findByCurrentStatus(detectWhenOffChainTaskStatuses)) { for (Replicate replicate : replicatesService.getReplicates(task.getChainTaskId())) { - Optional lastRelevantStatus = replicate.getLastRelevantStatus(); + ReplicateStatus lastRelevantStatus = replicate.getLastRelevantStatus(); - if (lastRelevantStatus.isEmpty() || lastRelevantStatus.get().equals(offchainCompleted)) { + if (lastRelevantStatus == offchainCompleted) { continue; } @@ -92,7 +91,7 @@ void dectectOnchainCompleted(List detectWhenOffChainTaskStatuses, if (statusTrueOnChain) { log.info("Detected confirmed missing update (replicate) [is:{}, should:{}, taskId:{}]", - lastRelevantStatus.get(), onchainCompleted, task.getChainTaskId()); + lastRelevantStatus, onchainCompleted, task.getChainTaskId()); updateReplicateStatuses(task, replicate, offchainCompleted); } } diff --git a/src/main/java/com/iexec/core/registry/PlatformRegistryConfiguration.java b/src/main/java/com/iexec/core/registry/PlatformRegistryConfiguration.java new file mode 100644 index 000000000..da0982882 --- /dev/null +++ b/src/main/java/com/iexec/core/registry/PlatformRegistryConfiguration.java @@ -0,0 +1,20 @@ +package com.iexec.core.registry; + +import lombok.Getter; +import org.hibernate.validator.constraints.URL; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Getter +@Configuration +public class PlatformRegistryConfiguration { + + @URL + @Value("${sms.scone}") + private String sconeSms; + + @URL + @Value("${sms.gramine}") + private String gramineSms; + +} diff --git a/src/main/java/com/iexec/core/replicate/NoReplicateStatusException.java b/src/main/java/com/iexec/core/replicate/NoReplicateStatusException.java new file mode 100644 index 000000000..4f35eb208 --- /dev/null +++ b/src/main/java/com/iexec/core/replicate/NoReplicateStatusException.java @@ -0,0 +1,14 @@ +package com.iexec.core.replicate; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Exception that can be thrown + * when searching for a {@link com.iexec.common.replicate.ReplicateStatus} fails. + */ +@AllArgsConstructor +@Getter +public class NoReplicateStatusException extends RuntimeException { + private final String chainTaskId; +} diff --git a/src/main/java/com/iexec/core/replicate/Replicate.java b/src/main/java/com/iexec/core/replicate/Replicate.java index f2282b976..f87ec6c1a 100644 --- a/src/main/java/com/iexec/core/replicate/Replicate.java +++ b/src/main/java/com/iexec/core/replicate/Replicate.java @@ -19,20 +19,19 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.iexec.common.chain.ChainReceipt; -import com.iexec.common.replicate.ReplicateStatus; -import com.iexec.common.replicate.ReplicateStatusCause; -import com.iexec.common.replicate.ReplicateStatusDetails; -import com.iexec.common.replicate.ReplicateStatusModifier; -import com.iexec.common.replicate.ReplicateStatusUpdate; - +import com.iexec.common.replicate.*; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; import java.util.stream.Collectors; -import static com.iexec.common.replicate.ReplicateStatus.*; -import static com.iexec.common.replicate.ReplicateStatusUpdate.*; +import static com.iexec.common.replicate.ReplicateStatus.CREATED; +import static com.iexec.common.replicate.ReplicateStatus.WORKER_LOST; +import static com.iexec.common.replicate.ReplicateStatusUpdate.poolManagerRequest; @Data @@ -40,7 +39,11 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class Replicate { - private List statusUpdateList; + // FIXME: should be final + private List statusUpdateList = new ArrayList<>( + // a new replicate should only be created by the scheduler + List.of(poolManagerRequest(CREATED)) + ); private String walletAddress; private String resultLink; private String chainCallbackData; @@ -52,9 +55,6 @@ public class Replicate { public Replicate(String walletAddress, String chainTaskId) { this.chainTaskId = chainTaskId; this.walletAddress = walletAddress; - this.statusUpdateList = new ArrayList<>(); - // a new replicate should only be create by the scheduler - this.statusUpdateList.add(poolManagerRequest(CREATED)); this.contributionHash = ""; } @@ -64,10 +64,10 @@ public ReplicateStatus getCurrentStatus() { } @JsonIgnore - public Optional getLastRelevantStatus() { // FIXME: remove Optional and add a no-args constructor + public ReplicateStatus getLastRelevantStatus() { // ignore cases like: WORKER_LOST and RECOVERING - List statusList = getStatusUpdateList().stream() + List statusList = statusUpdateList.stream() .map(ReplicateStatusUpdate::getStatus) .collect(Collectors.toList()); @@ -77,21 +77,21 @@ public Optional getLastRelevantStatus() { // FIXME: remove Opt for (int i = statusList.size() - 1; i >= 0; i--) { if (!ignoredStatuses.contains(statusList.get(i))) { - return Optional.of(statusList.get(i)); + return statusList.get(i); } } - return Optional.empty(); + throw new NoReplicateStatusException(chainTaskId); } @JsonIgnore public ReplicateStatus getLastButOneStatus() { - return this.getStatusUpdateList().get(this.getStatusUpdateList().size() - 2).getStatus(); + return statusUpdateList.get(statusUpdateList.size() - 2).getStatus(); } @JsonIgnore private ReplicateStatusUpdate getLatestStatusUpdate() { - return this.getStatusUpdateList().get(this.getStatusUpdateList().size() - 1); + return statusUpdateList.get(statusUpdateList.size() - 1); } public boolean updateStatus(ReplicateStatus newStatus, ReplicateStatusModifier modifier) { @@ -115,7 +115,7 @@ public boolean updateStatus(ReplicateStatusUpdate statusUpdate) { } public boolean containsStatus(ReplicateStatus replicateStatus) { - for (ReplicateStatusUpdate replicateStatusUpdate : this.getStatusUpdateList()) { + for (ReplicateStatusUpdate replicateStatusUpdate : statusUpdateList) { if (replicateStatusUpdate.getStatus().equals(replicateStatus)) { return true; } @@ -132,7 +132,7 @@ public boolean containsRevealedStatus() { } public boolean isCreatedMoreThanNPeriodsAgo(int numberPeriod, long maxExecutionTime) { - Date creationDate = this.getStatusUpdateList().get(0).getDate(); + Date creationDate = statusUpdateList.get(0).getDate(); Date numberPeriodsAfterCreationDate = new Date(creationDate.getTime() + numberPeriod * maxExecutionTime); Date now = new Date(); @@ -149,21 +149,19 @@ public boolean isBusyComputing() { } public boolean isRecoverable() { - Optional currentStatus = getLastRelevantStatus(); - if (currentStatus.isEmpty()) return false; - return ReplicateStatus.isRecoverable(currentStatus.get()); + ReplicateStatus currentStatus = getLastRelevantStatus(); + return ReplicateStatus.isRecoverable(currentStatus); } public boolean isBeforeStatus(ReplicateStatus status) { - Optional currentStatus = getLastRelevantStatus(); - if (currentStatus.isEmpty()) return false; - return currentStatus.get().ordinal() < status.ordinal(); + ReplicateStatus currentStatus = getLastRelevantStatus(); + return currentStatus.ordinal() < status.ordinal(); } boolean isStatusBeforeWorkerLostEqualsTo(ReplicateStatus status) { - int size = getStatusUpdateList().size(); + int size = statusUpdateList.size(); return size >= 2 - && getStatusUpdateList().get(size - 1).getStatus().equals(WORKER_LOST) - && getStatusUpdateList().get(size - 2).getStatus().equals(status); + && statusUpdateList.get(size - 1).getStatus().equals(WORKER_LOST) + && statusUpdateList.get(size - 2).getStatus().equals(status); } } diff --git a/src/main/java/com/iexec/core/replicate/ReplicateSupplyService.java b/src/main/java/com/iexec/core/replicate/ReplicateSupplyService.java index 60ada9cbf..5d632ec6b 100644 --- a/src/main/java/com/iexec/core/replicate/ReplicateSupplyService.java +++ b/src/main/java/com/iexec/core/replicate/ReplicateSupplyService.java @@ -17,12 +17,16 @@ package com.iexec.core.replicate; import com.iexec.common.chain.WorkerpoolAuthorization; +import com.iexec.common.lifecycle.purge.ExpiringTaskMapFactory; +import com.iexec.common.lifecycle.purge.Purgeable; import com.iexec.common.notification.TaskNotification; import com.iexec.common.notification.TaskNotificationExtra; import com.iexec.common.notification.TaskNotificationType; import com.iexec.common.replicate.ReplicateStatus; import com.iexec.common.replicate.ReplicateStatusDetails; import com.iexec.common.replicate.ReplicateStatusUpdate; +import com.iexec.common.replicate.ReplicateTaskSummary; +import com.iexec.common.replicate.ReplicateTaskSummary.ReplicateTaskSummaryBuilder; import com.iexec.common.task.TaskAbortCause; import com.iexec.core.chain.SignatureService; import com.iexec.core.chain.Web3jService; @@ -33,22 +37,19 @@ import com.iexec.core.task.update.TaskUpdateRequestManager; import com.iexec.core.worker.Worker; import com.iexec.core.worker.WorkerService; -import net.jodah.expiringmap.ExpiringMap; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static com.iexec.common.replicate.ReplicateStatus.*; -import static com.iexec.core.task.Task.LONGEST_TASK_TIMEOUT; @Service -public class ReplicateSupplyService { +public class ReplicateSupplyService implements Purgeable { private final ReplicatesService replicatesService; private final SignatureService signatureService; @@ -56,10 +57,7 @@ public class ReplicateSupplyService { private final TaskUpdateRequestManager taskUpdateRequestManager; private final WorkerService workerService; private final Web3jService web3jService; - final Map taskAccessForNewReplicateLocks = - ExpiringMap.builder() - .expiration(LONGEST_TASK_TIMEOUT.getSeconds(), TimeUnit.SECONDS) - .build(); + final Map taskAccessForNewReplicateLocks = ExpiringTaskMapFactory.getExpiringTaskMap(); public ReplicateSupplyService(ReplicatesService replicatesService, SignatureService signatureService, @@ -87,7 +85,7 @@ public ReplicateSupplyService(ReplicatesService replicatesService, * */ @Retryable(value = {OptimisticLockingFailureException.class}, maxAttempts = 5) - Optional getAuthOfAvailableReplicate(long workerLastBlock, String walletAddress) { + Optional getAvailableReplicateTaskSummary(long workerLastBlock, String walletAddress) { // return empty if max computing task is reached or if the worker is not found if (!workerService.canAcceptMoreWorks(walletAddress)) { return Optional.empty(); @@ -112,7 +110,7 @@ Optional getAuthOfAvailableReplicate(long workerLastBlo } Worker worker = optional.get(); - return getAuthorizationForAnyAvailableTask( + return getReplicateTaskSummaryForAnyAvailableTask( walletAddress, worker.isTeeEnabled() ); @@ -124,17 +122,17 @@ Optional getAuthOfAvailableReplicate(long workerLastBlo * * @param walletAddress Wallet address of the worker asking for work. * @param isTeeEnabled Whether this worker supports TEE. - * @return An {@link Optional} containing a {@link WorkerpoolAuthorization} + * @return An {@link Optional} containing a {@link ReplicateTaskSummary} * if any {@link Task} is available and can be handled by this worker, * {@link Optional#empty()} otherwise. */ - private Optional getAuthorizationForAnyAvailableTask( + private Optional getReplicateTaskSummaryForAnyAvailableTask( String walletAddress, boolean isTeeEnabled) { final List alreadyScannedTasks = new ArrayList<>(); - Optional authorization = Optional.empty(); - while (authorization.isEmpty()) { + Optional replicateTaskSummary = Optional.empty(); + while (replicateTaskSummary.isEmpty()) { final Optional oTask = taskService.getPrioritizedInitializedOrRunningTask( !isTeeEnabled, alreadyScannedTasks @@ -146,12 +144,12 @@ private Optional getAuthorizationForAnyAvailableTask( final Task task = oTask.get(); alreadyScannedTasks.add(task.getChainTaskId()); - authorization = getAuthorizationForTask(task, walletAddress); + replicateTaskSummary = getReplicateTaskSummary(task, walletAddress); } - return authorization; + return replicateTaskSummary; } - private Optional getAuthorizationForTask(Task task, String walletAddress) { + private Optional getReplicateTaskSummary(Task task, String walletAddress) { String chainTaskId = task.getChainTaskId(); if (!acceptOrRejectTask(task, walletAddress)) { return Optional.empty(); @@ -162,7 +160,12 @@ private Optional getAuthorizationForTask(Task task, Str walletAddress, chainTaskId, task.getEnclaveChallenge()); - return Optional.of(authorization); + ReplicateTaskSummaryBuilder replicateTaskSummary = ReplicateTaskSummary.builder() + .workerpoolAuthorization(authorization); + if(task.isTeeTask()){ + replicateTaskSummary.smsUrl(task.getSmsUrl()); + } + return Optional.of(replicateTaskSummary.build()); } /** @@ -342,12 +345,8 @@ private Optional recoverReplicateInContributionPhase(Task String chainTaskId = task.getChainTaskId(); String walletAddress = replicate.getWalletAddress(); - if (replicate.getLastRelevantStatus().isEmpty()) { - return Optional.empty(); - } - boolean beforeContributing = replicate.isBeforeStatus(ReplicateStatus.CONTRIBUTING); - boolean didReplicateStartContributing = replicate.getLastRelevantStatus().get().equals(ReplicateStatus.CONTRIBUTING); + boolean didReplicateStartContributing = replicate.getLastRelevantStatus() == ReplicateStatus.CONTRIBUTING; boolean didReplicateContributeOnChain = replicatesService.didReplicateContributeOnchain(chainTaskId, walletAddress); if (beforeContributing) { @@ -370,12 +369,8 @@ private Optional recoverReplicateInContributionPhase(Task } Replicate replicateWithLatestChanges = oReplicateWithLatestChanges.get(); - if (replicateWithLatestChanges.getLastRelevantStatus().isEmpty()) { - return Optional.empty(); - } - - boolean didReplicateContribute = replicateWithLatestChanges.getLastRelevantStatus().get() - .equals(ReplicateStatus.CONTRIBUTED); + boolean didReplicateContribute = replicateWithLatestChanges.getLastRelevantStatus() + == ReplicateStatus.CONTRIBUTED; if (didReplicateContribute) { final Optional oReplicatesList = replicatesService.getReplicatesList(chainTaskId); @@ -405,12 +400,8 @@ private Optional recoverReplicateInRevealPhase(Task task, String chainTaskId = task.getChainTaskId(); String walletAddress = replicate.getWalletAddress(); - if (replicate.getLastRelevantStatus().isEmpty()) { - return Optional.empty(); - } - - boolean isInStatusContributed = replicate.getLastRelevantStatus().get().equals(ReplicateStatus.CONTRIBUTED); - boolean didReplicateStartRevealing = replicate.getLastRelevantStatus().get().equals(ReplicateStatus.REVEALING); + boolean isInStatusContributed = replicate.getLastRelevantStatus() == ReplicateStatus.CONTRIBUTED; + boolean didReplicateStartRevealing = replicate.getLastRelevantStatus() == ReplicateStatus.REVEALING; boolean didReplicateRevealOnChain = replicatesService.didReplicateRevealOnchain(chainTaskId, walletAddress); if (isInStatusContributed) { @@ -434,15 +425,12 @@ private Optional recoverReplicateInRevealPhase(Task task, return Optional.empty(); } replicate = oReplicateWithLatestChanges.get(); - if (replicate.getLastRelevantStatus().isEmpty()) { - return Optional.empty(); - } - boolean didReplicateReveal = replicate.getLastRelevantStatus().get() - .equals(ReplicateStatus.REVEALED); + boolean didReplicateReveal = replicate.getLastRelevantStatus() + == ReplicateStatus.REVEALED; - boolean wasReplicateRequestedToUpload = replicate.getLastRelevantStatus().get() - .equals(ReplicateStatus.RESULT_UPLOAD_REQUESTED); + boolean wasReplicateRequestedToUpload = replicate.getLastRelevantStatus() + == ReplicateStatus.RESULT_UPLOAD_REQUESTED; if (didReplicateReveal) { return Optional.of(TaskNotificationType.PLEASE_WAIT); @@ -467,14 +455,10 @@ private Optional recoverReplicateInResultUploadPhase(Task String chainTaskId = task.getChainTaskId(); String walletAddress = replicate.getWalletAddress(); - if (replicate.getLastRelevantStatus().isEmpty()) { - return Optional.empty(); - } - - boolean wasReplicateRequestedToUpload = replicate.getLastRelevantStatus().get().equals(ReplicateStatus.RESULT_UPLOAD_REQUESTED); - boolean didReplicateStartUploading = replicate.getLastRelevantStatus().get().equals(ReplicateStatus.RESULT_UPLOADING); + boolean wasReplicateRequestedToUpload = replicate.getLastRelevantStatus() == ReplicateStatus.RESULT_UPLOAD_REQUESTED; + boolean didReplicateStartUploading = replicate.getLastRelevantStatus() == ReplicateStatus.RESULT_UPLOADING; boolean didReplicateUploadWithoutNotifying = replicatesService.isResultUploaded(task.getChainTaskId()); - boolean hasReplicateAlreadyUploaded = replicate.getLastRelevantStatus().get().equals(ReplicateStatus.RESULT_UPLOADED); + boolean hasReplicateAlreadyUploaded = replicate.getLastRelevantStatus() == ReplicateStatus.RESULT_UPLOADED; if (wasReplicateRequestedToUpload) { return Optional.of(TaskNotificationType.PLEASE_UPLOAD); @@ -532,4 +516,17 @@ private TaskAbortCause getTaskAbortCause(Task task) { return TaskAbortCause.UNKNOWN; } } + + // region purge locks + @Override + public boolean purgeTask(String chainTaskId) { + taskAccessForNewReplicateLocks.remove(chainTaskId); + return !taskAccessForNewReplicateLocks.containsKey(chainTaskId); + } + + @Override + public void purgeAllTasksData() { + taskAccessForNewReplicateLocks.clear(); + } + // endregion } diff --git a/src/main/java/com/iexec/core/replicate/ReplicatesController.java b/src/main/java/com/iexec/core/replicate/ReplicatesController.java index 0c1a2f8e1..0b302b653 100644 --- a/src/main/java/com/iexec/core/replicate/ReplicatesController.java +++ b/src/main/java/com/iexec/core/replicate/ReplicatesController.java @@ -16,7 +16,6 @@ package com.iexec.core.replicate; -import com.iexec.common.chain.WorkerpoolAuthorization; import com.iexec.common.notification.TaskNotification; import com.iexec.common.notification.TaskNotificationType; import com.iexec.common.replicate.*; @@ -50,21 +49,21 @@ public ReplicatesController(ReplicatesService replicatesService, } @GetMapping("/replicates/available") - public ResponseEntity getAvailableReplicate( + public ResponseEntity getAvailableReplicateTaskSummary( @RequestParam(name = "blockNumber") long blockNumber, @RequestHeader("Authorization") String bearerToken) { String workerWalletAddress = jwtTokenProvider.getWalletAddressFromBearerToken(bearerToken); if (workerWalletAddress.isEmpty()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED.value()).build(); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } - if (!workerService.isWorkerAllowedToAskReplicate(workerWalletAddress)){ - return ResponseEntity.status(HttpStatus.NO_CONTENT.value()).build(); + if (!workerService.isWorkerAllowedToAskReplicate(workerWalletAddress)) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } workerService.updateLastReplicateDemandDate(workerWalletAddress); return replicateSupplyService - .getAuthOfAvailableReplicate(blockNumber, workerWalletAddress) + .getAvailableReplicateTaskSummary(blockNumber, workerWalletAddress) .map(ResponseEntity::ok) .orElseGet(() -> status(HttpStatus.NO_CONTENT).build()); } @@ -76,7 +75,7 @@ public ResponseEntity> getMissedTaskNotifications( String workerWalletAddress = jwtTokenProvider.getWalletAddressFromBearerToken(bearerToken); if (workerWalletAddress.isEmpty()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED.value()).build(); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } List missedTaskNotifications = @@ -85,6 +84,17 @@ public ResponseEntity> getMissedTaskNotifications( return ResponseEntity.ok(missedTaskNotifications); } + /** + * Handles workers requests to update a replicate status. + *

+ * The scheduler response can only be null on authentication failures. + * In all other situations, a notification must be sent and the body cannot be null. + * + * @param bearerToken Authentication token of a worker. + * @param chainTaskId ID of the task on which the worker has an update. + * @param statusUpdate Status update sent by the worker. + * @return A notification to the worker. A notification is implemented in {@code TaskNotificationType}. + */ @PostMapping("/replicates/{chainTaskId}/updateStatus") public ResponseEntity updateReplicateStatus( @RequestHeader("Authorization") String bearerToken, @@ -94,7 +104,7 @@ public ResponseEntity updateReplicateStatus( String walletAddress = jwtTokenProvider.getWalletAddressFromBearerToken(bearerToken); if (walletAddress.isEmpty()) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED.value()).build(); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } statusUpdate.setModifier(ReplicateStatusModifier.WORKER); @@ -125,16 +135,17 @@ public ResponseEntity updateReplicateStatus( return replicatesService .updateReplicateStatus(chainTaskId, walletAddress, statusUpdate, updateReplicateStatusArgs) .map(ResponseEntity::ok) - .orElse(ResponseEntity.status(HttpStatus.FORBIDDEN.value()) - .build()); + .orElse(ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(TaskNotificationType.PLEASE_ABORT)); case ALREADY_REPORTED: - return status(HttpStatus.ALREADY_REPORTED.value()) + return ResponseEntity.status(HttpStatus.ALREADY_REPORTED) .body(TaskNotificationType.PLEASE_WAIT); case UNKNOWN_REPLICATE: case BAD_WORKFLOW_TRANSITION: case GENERIC_CANT_UPDATE: default: - return ResponseEntity.status(HttpStatus.FORBIDDEN.value()).build(); + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(TaskNotificationType.PLEASE_ABORT); } } } diff --git a/src/main/java/com/iexec/core/replicate/ReplicatesList.java b/src/main/java/com/iexec/core/replicate/ReplicatesList.java index 5449b16a5..96651a248 100644 --- a/src/main/java/com/iexec/core/replicate/ReplicatesList.java +++ b/src/main/java/com/iexec/core/replicate/ReplicatesList.java @@ -69,8 +69,8 @@ public ReplicatesList(String chainTaskId, List replicates) { public int getNbValidContributedWinners(String contributionHash) { int nbValidWinners = 0; for (Replicate replicate : replicates) { - Optional oStatus = replicate.getLastRelevantStatus(); - if (oStatus.isPresent() && oStatus.get().equals(CONTRIBUTED) + ReplicateStatus status = replicate.getLastRelevantStatus(); + if (status == CONTRIBUTED && contributionHash.equals(replicate.getContributionHash())) { nbValidWinners++; } @@ -94,7 +94,7 @@ public int getNbReplicatesWithLastRelevantStatus(ReplicateStatus... listStatus) int nbReplicates = 0; for (Replicate replicate : replicates) { for (ReplicateStatus status : listStatus) { - if (Objects.equals(replicate.getLastRelevantStatus().orElse(null), status)) { + if (replicate.getLastRelevantStatus() == status) { nbReplicates++; } } diff --git a/src/main/java/com/iexec/core/replicate/ReplicatesService.java b/src/main/java/com/iexec/core/replicate/ReplicatesService.java index 5c5da8949..06b965530 100644 --- a/src/main/java/com/iexec/core/replicate/ReplicatesService.java +++ b/src/main/java/com/iexec/core/replicate/ReplicatesService.java @@ -608,12 +608,8 @@ public boolean didReplicateRevealOnchain(String chainTaskId, String walletAddres } public void setRevealTimeoutStatusIfNeeded(String chainTaskId, Replicate replicate) { - Optional oStatus = replicate.getLastRelevantStatus(); - if (oStatus.isEmpty()) { - return; - } - ReplicateStatus status = oStatus.get(); - if (status.equals(REVEALING) || status.equals(CONTRIBUTED)) { + ReplicateStatus status = replicate.getLastRelevantStatus(); + if (status == REVEALING || status == CONTRIBUTED) { ReplicateStatusUpdate statusUpdate = ReplicateStatusUpdate.poolManagerRequest(FAILED, REVEAL_TIMEOUT); updateReplicateStatus(chainTaskId, replicate.getWalletAddress(), statusUpdate); } diff --git a/src/main/java/com/iexec/core/security/ChallengeService.java b/src/main/java/com/iexec/core/security/ChallengeService.java index dc05de1c4..c7be212e2 100644 --- a/src/main/java/com/iexec/core/security/ChallengeService.java +++ b/src/main/java/com/iexec/core/security/ChallengeService.java @@ -18,28 +18,33 @@ import net.jodah.expiringmap.ExpirationPolicy; import net.jodah.expiringmap.ExpiringMap; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.stereotype.Service; +import java.security.SecureRandom; +import java.util.Base64; import java.util.concurrent.TimeUnit; @Service public class ChallengeService { - // Map - // this map will automatically delete entries older than one hour, ExpiringMap is thread-safe - private final ExpiringMap challengeMap; + private final ExpiringMap challengesMap = ExpiringMap.builder() + .expiration(5, TimeUnit.MINUTES) + .expirationPolicy(ExpirationPolicy.CREATED) + .build(); - ChallengeService() { - this.challengeMap = ExpiringMap.builder() - .expiration(60, TimeUnit.MINUTES) - .expirationPolicy(ExpirationPolicy.CREATED) - .build(); + public String computeChallenge() { + SecureRandom secureRandom = new SecureRandom(); + byte[] seed = new byte[32]; + secureRandom.nextBytes(seed); + return Base64.getEncoder().encodeToString(seed); } public String getChallenge(String workerWallet) { - String challenge = RandomStringUtils.randomAlphabetic(10); - challengeMap.putIfAbsent(workerWallet, challenge); - return challengeMap.get(workerWallet); + return challengesMap.computeIfAbsent(workerWallet, wallet -> computeChallenge()); } + + public void removeChallenge(String workerWallet, String challenge) { + challengesMap.remove(workerWallet, challenge); + } + } diff --git a/src/main/java/com/iexec/core/security/EIP712ChallengeService.java b/src/main/java/com/iexec/core/security/EIP712ChallengeService.java index 91740ef6a..c7e2106c5 100644 --- a/src/main/java/com/iexec/core/security/EIP712ChallengeService.java +++ b/src/main/java/com/iexec/core/security/EIP712ChallengeService.java @@ -16,10 +16,9 @@ package com.iexec.core.security; +import com.iexec.common.chain.eip712.EIP712Domain; import com.iexec.common.chain.eip712.entity.Challenge; import com.iexec.common.chain.eip712.entity.EIP712Challenge; -import com.iexec.common.chain.eip712.EIP712Domain; -import lombok.extern.slf4j.Slf4j; import net.jodah.expiringmap.ExpirationPolicy; import net.jodah.expiringmap.ExpiringMap; import org.springframework.stereotype.Service; @@ -29,7 +28,6 @@ import java.util.Base64; import java.util.concurrent.TimeUnit; -@Slf4j @Service public class EIP712ChallengeService { diff --git a/src/main/java/com/iexec/core/security/JwtTokenProvider.java b/src/main/java/com/iexec/core/security/JwtTokenProvider.java index 5c46088f9..2b2ed3e55 100644 --- a/src/main/java/com/iexec/core/security/JwtTokenProvider.java +++ b/src/main/java/com/iexec/core/security/JwtTokenProvider.java @@ -16,39 +16,55 @@ package com.iexec.core.security; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.info.BuildProperties; import org.springframework.stereotype.Component; import java.security.SecureRandom; import java.util.Base64; import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; @Slf4j @Component public class JwtTokenProvider { - private final ChallengeService challengeService; - private final String secretKey; + static final int KEY_SIZE = 128; + private static final long TOKEN_VALIDITY_DURATION = 1000L * 60 * 60; + private final ConcurrentHashMap jwTokensMap = new ConcurrentHashMap<>(); + private final String applicationId; + private final byte[] secretKey = new byte[KEY_SIZE]; - public JwtTokenProvider(ChallengeService challengeService) { - this.challengeService = challengeService; + public JwtTokenProvider(BuildProperties buildProperties) { + this.applicationId = "iExec Scheduler v" + buildProperties.getVersion(); SecureRandom secureRandom = new SecureRandom(); - byte[] seed = new byte[32]; - secureRandom.nextBytes(seed); - this.secretKey = Base64.getEncoder().encodeToString(seed); + secureRandom.nextBytes(secretKey); } - public String createToken(String walletAddress) { - return Jwts.builder() - .setAudience(walletAddress) - .setIssuedAt(new Date()) - .setSubject(challengeService.getChallenge(walletAddress)) - .signWith(SignatureAlgorithm.HS256, secretKey) - .compact(); + /** + * Creates a signed JWT with expiration date for a given ethereum address. + *

+ * The token is cached. It might be pruned in best effort mode by other processes founding that token is expired. + * @param walletAddress worker address for which the token is created + * @return A signed JWT for a given ethereum address + */ + public String getOrCreateToken(String walletAddress) { + // Do not try to check if JWT is valid here, it introduces too many questions on challenge validity, + // concurrency of operations and potential race conditions. + // When a token is presented, scheduler answers UNAUTHORIZED if the JWT is invalid and purges caches + // on expiration of a known JWT. + return jwTokensMap.computeIfAbsent(walletAddress, address -> { + Date now = new Date(); + return Jwts.builder() + .setAudience(applicationId) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + TOKEN_VALIDITY_DURATION)) + .setSubject(address) + .signWith(Keys.hmacShaKeyFor(secretKey), SignatureAlgorithm.HS256) + .compact(); + }); } public String resolveToken(String token) { @@ -58,46 +74,52 @@ public String resolveToken(String token) { return null; } - /* - * IMPORTANT /!\ - * Having the same validity duration for both challenge - * and jwtoken can cause a problem. The latter should be - * slightly longer (in minutes). In this case the challenge - * is valid for 60 minutes while jwtoken stays valid - * for 65 minutes. - * - * Problem description: - * 1) jwtString expires - * 2) worker gets old challenge - * 3) old challenge expires - * 4) worker tries logging with old challenge + /** + * Checks if a JWT is valid. + *

+ * A valid JWT must: + *

* * @param statuses The task status should be one of this list. - * @param excludedTag The task tag should not be this tag + * @param excludedTags The task tag should not be one this tag list * - use {@literal null} if no tag should be excluded. * @param excludedChainTaskIds The chain task ID should not be one of this list. * @param sort How to prioritize tasks. * @return The first task matching with the criteria, according to the {@code sort} parameter. */ - Optional findFirstByCurrentStatusInAndTagNotAndChainTaskIdNotIn(List statuses, String excludedTag, List excludedChainTaskIds, Sort sort); + Optional findFirstByCurrentStatusInAndTagNotInAndChainTaskIdNotIn(List statuses, List excludedTags, List excludedChainTaskIds, Sort sort); @Query("{ 'currentStatus': {$nin: ?0} }") List findByCurrentStatusNotIn(List statuses); diff --git a/src/main/java/com/iexec/core/task/TaskService.java b/src/main/java/com/iexec/core/task/TaskService.java index 55a04c29e..cb8f6cb76 100644 --- a/src/main/java/com/iexec/core/task/TaskService.java +++ b/src/main/java/com/iexec/core/task/TaskService.java @@ -21,8 +21,8 @@ import com.iexec.common.tee.TeeUtils; import com.iexec.core.chain.IexecHubService; import com.iexec.core.replicate.ReplicatesList; -import com.iexec.core.replicate.ReplicatesService; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -39,14 +39,11 @@ public class TaskService { private final TaskRepository taskRepository; private final IexecHubService iexecHubService; - private final ReplicatesService replicatesService; public TaskService(TaskRepository taskRepository, - IexecHubService iexecHubService, - ReplicatesService replicatesService) { + IexecHubService iexecHubService) { this.taskRepository = taskRepository; this.iexecHubService = iexecHubService; - this.replicatesService = replicatesService; } /** @@ -78,26 +75,23 @@ public Optional addTask( Date contributionDeadline, Date finalDeadline ) { - return taskRepository - .findByChainDealIdAndTaskIndex(chainDealId, taskIndex) - .>map(task -> { - log.info("Task already added [chainDealId:{}, taskIndex:{}, " + - "imageName:{}, commandLine:{}, trust:{}]", chainDealId, - taskIndex, imageName, commandLine, trust); - return Optional.empty(); - }) - .orElseGet(() -> { - Task newTask = new Task(chainDealId, taskIndex, imageName, - commandLine, trust, maxExecutionTime, tag); - newTask.setDealBlockNumber(dealBlockNumber); - newTask.setFinalDeadline(finalDeadline); - newTask.setContributionDeadline(contributionDeadline); - newTask = taskRepository.save(newTask); - log.info("Added new task [chainDealId:{}, taskIndex:{}, imageName:{}, " + - "commandLine:{}, trust:{}, chainTaskId:{}]", chainDealId, - taskIndex, imageName, commandLine, trust, newTask.getChainTaskId()); - return Optional.of(newTask); - }); + Task newTask = new Task(chainDealId, taskIndex, imageName, + commandLine, trust, maxExecutionTime, tag); + newTask.setDealBlockNumber(dealBlockNumber); + newTask.setFinalDeadline(finalDeadline); + newTask.setContributionDeadline(contributionDeadline); + try { + newTask = taskRepository.save(newTask); + log.info("Added new task [chainDealId:{}, taskIndex:{}, imageName:{}, " + + "commandLine:{}, trust:{}, chainTaskId:{}]", chainDealId, + taskIndex, imageName, commandLine, trust, newTask.getChainTaskId()); + return Optional.of(newTask); + } catch (DuplicateKeyException e) { + log.info("Task already added [chainDealId:{}, taskIndex:{}, " + + "imageName:{}, commandLine:{}, trust:{}]", chainDealId, + taskIndex, imageName, commandLine, trust); + return Optional.empty(); + } } /** @@ -149,19 +143,19 @@ public List findByCurrentStatus(List statusList) { public Optional getPrioritizedInitializedOrRunningTask( boolean shouldExcludeTeeTasks, List excludedChainTaskIds) { - final String excludedTag = shouldExcludeTeeTasks - ? TeeUtils.TEE_TAG + final List excludedTags = shouldExcludeTeeTasks + ? List.of(TeeUtils.TEE_SCONE_ONLY_TAG, TeeUtils.TEE_GRAMINE_ONLY_TAG) : null; return findPrioritizedTask( Arrays.asList(INITIALIZED, RUNNING), - excludedTag, + excludedTags, excludedChainTaskIds, Sort.by(Sort.Order.desc(Task.CURRENT_STATUS_FIELD_NAME), Sort.Order.asc(Task.CONTRIBUTION_DEADLINE_FIELD_NAME))); } /** - * Shortcut for {@link TaskRepository#findFirstByCurrentStatusInAndTagNotAndChainTaskIdNotIn}. + * Shortcut for {@link TaskRepository#findFirstByCurrentStatusInAndTagNotInAndChainTaskIdNotIn}. * Retrieves the prioritized task matching with given criteria: *
    *
  • Task is in one of given {@code statuses};
  • @@ -180,12 +174,12 @@ public Optional getPrioritizedInitializedOrRunningTask( * @return The first task matching with the criteria, according to the {@code sort} parameter. */ private Optional findPrioritizedTask(List statuses, - String excludedTag, + List excludedTags, List excludedChainTaskIds, Sort sort) { - return taskRepository.findFirstByCurrentStatusInAndTagNotAndChainTaskIdNotIn( + return taskRepository.findFirstByCurrentStatusInAndTagNotInAndChainTaskIdNotIn( statuses, - excludedTag, + excludedTags, excludedChainTaskIds, sort ); diff --git a/src/main/java/com/iexec/core/task/listener/TaskListeners.java b/src/main/java/com/iexec/core/task/listener/TaskListeners.java index 7948527d6..a2ee4d0db 100644 --- a/src/main/java/com/iexec/core/task/listener/TaskListeners.java +++ b/src/main/java/com/iexec/core/task/listener/TaskListeners.java @@ -16,6 +16,7 @@ package com.iexec.core.task.listener; +import com.iexec.common.lifecycle.purge.PurgeService; import com.iexec.common.notification.TaskNotification; import com.iexec.common.notification.TaskNotificationExtra; import com.iexec.common.notification.TaskNotificationType; @@ -43,15 +44,18 @@ public class TaskListeners { private final NotificationService notificationService; private final ReplicatesService replicatesService; private final WorkerService workerService; + private final PurgeService purgeService; public TaskListeners(TaskUpdateRequestManager taskUpdateRequestManager, NotificationService notificationService, ReplicatesService replicatesService, - WorkerService workerService) { + WorkerService workerService, + PurgeService purgeService) { this.taskUpdateRequestManager = taskUpdateRequestManager; this.notificationService = notificationService; this.replicatesService = replicatesService; this.workerService = workerService; + this.purgeService = purgeService; } @@ -163,7 +167,7 @@ public void onTaskCompletedEvent(TaskCompletedEvent event) { .workersAddress(Collections.emptyList()) .build()); - removeChainTaskIdFromWorkers(chainTaskId); + purgeTask(chainTaskId); } @EventListener @@ -177,7 +181,7 @@ public void onTaskFailedEvent(TaskFailedEvent event) { .workersAddress(Collections.emptyList()) .build()); - removeChainTaskIdFromWorkers(chainTaskId); + purgeTask(chainTaskId); } @EventListener @@ -191,13 +195,17 @@ public void onTaskRunningFailedEvent(TaskRunningFailedEvent event) { .workersAddress(Collections.emptyList()) .build()); - removeChainTaskIdFromWorkers(chainTaskId); + purgeTask(chainTaskId); } - private void removeChainTaskIdFromWorkers(String chainTaskId) { + private void purgeTask(String chainTaskId) { + // Remove task from workers for (Replicate replicate : replicatesService.getReplicates(chainTaskId)) { workerService.removeChainTaskIdFromWorker(chainTaskId, replicate.getWalletAddress()); } + + // Remove other services in-mem task info + purgeService.purgeAllServices(chainTaskId); } } diff --git a/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java b/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java index 384ca7f00..7d65e2d26 100644 --- a/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java +++ b/src/main/java/com/iexec/core/task/update/TaskUpdateManager.java @@ -202,13 +202,25 @@ void received2Initializing(Task task) { return; } + if (task.isTeeTask()) { + Optional smsUrl = smsService.getVerifiedSmsUrl(task.getChainTaskId(), task.getTag()); + if(smsUrl.isEmpty()){ + log.error("Couldn't get verified SMS url [chainTaskId: {}]", task.getChainTaskId()); + updateTaskStatusAndSave(task, INITIALIZE_FAILED); + updateTaskStatusAndSave(task, FAILED); + return; + } + task.setSmsUrl(smsUrl.get()); //SMS URL source of truth for the task + taskService.updateTask(task); + } + blockchainAdapterService .requestInitialize(task.getChainDealId(), task.getTaskIndex()) .filter(chainTaskId -> chainTaskId.equalsIgnoreCase(task.getChainTaskId())) .ifPresentOrElse(chainTaskId -> { log.info("Requested initialize on blockchain [chainTaskId:{}]", task.getChainTaskId()); - final Optional enclaveChallenge = smsService.getEnclaveChallenge(chainTaskId, task.isTeeTask()); + final Optional enclaveChallenge = smsService.getEnclaveChallenge(chainTaskId, task.getSmsUrl()); if (enclaveChallenge.isEmpty()) { log.error("Can't initialize task, enclave challenge is empty" + " [chainTaskId:{}]", chainTaskId); @@ -365,7 +377,6 @@ void running2RunningFailed(Task task) { boolean notAllReplicatesFailed = replicatesOfAliveWorkers .stream() .map(Replicate::getLastRelevantStatus) - .map(Optional::get) .anyMatch(Predicate.not(ReplicateStatus::isFailedBeforeComputed)); if (notAllReplicatesFailed) { @@ -486,8 +497,7 @@ void resultUploading2Uploaded(Task task) { boolean isReplicateUploading = replicate.getCurrentStatus() == ReplicateStatus.RESULT_UPLOADING; boolean isReplicateRecoveringToUpload = replicate.getCurrentStatus() == ReplicateStatus.RECOVERING && - replicate.getLastRelevantStatus().isPresent() && - replicate.getLastRelevantStatus().get() == ReplicateStatus.RESULT_UPLOADING; + replicate.getLastRelevantStatus() == ReplicateStatus.RESULT_UPLOADING; if (!isReplicateUploading && !isReplicateRecoveringToUpload) { requestUpload(task); diff --git a/src/main/java/com/iexec/core/task/update/TaskUpdateRequestManager.java b/src/main/java/com/iexec/core/task/update/TaskUpdateRequestManager.java index 1d68612ea..3cda6d4a2 100644 --- a/src/main/java/com/iexec/core/task/update/TaskUpdateRequestManager.java +++ b/src/main/java/com/iexec/core/task/update/TaskUpdateRequestManager.java @@ -26,7 +26,7 @@ import java.util.concurrent.*; import java.util.function.Supplier; -import static com.iexec.core.task.Task.LONGEST_TASK_TIMEOUT; +import static com.iexec.common.chain.CategoriesUtils.LONGEST_TASK_TIMEOUT; /** * This class is used to perform updates on a task one by one. diff --git a/src/main/java/com/iexec/core/worker/WorkerController.java b/src/main/java/com/iexec/core/worker/WorkerController.java index 0ba14b1a5..31cf37567 100644 --- a/src/main/java/com/iexec/core/worker/WorkerController.java +++ b/src/main/java/com/iexec/core/worker/WorkerController.java @@ -88,7 +88,8 @@ public ResponseEntity getToken(@RequestParam(name = "walletAddress") Str if (SignatureUtils.doesSignatureMatchesAddress(signature.getR(), signature.getS(), BytesUtils.bytesToString(hashToCheck), walletAddress)) { - String token = jwtTokenProvider.createToken(walletAddress); + challengeService.removeChallenge(walletAddress, challenge); + String token = jwtTokenProvider.getOrCreateToken(walletAddress); return ok(token); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 85c4d9c1c..8aa0c0724 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,6 +8,10 @@ spring: host: ${MONGO_HOST:localhost} port: ${MONGO_PORT:13002} auto-index-creation: true # Auto-index creation is disabled by default starting with Spring Data MongoDB 3.x. + config.import: "configserver:${IEXEC_PLATFORM_REGISTRY}" # configserver:http://platform-registry:8888 + cloud.config: + profile: ${IEXEC_PLATFORM_REGISTRY_STACK:} # mainnet, bellecour3, 1234, .. + label: ${IEXEC_PLATFORM_REGISTRY_LABEL:} # main, develop, v10, 07998be mongock: runner-type: InitializingBean change-logs-scan-package: @@ -44,11 +48,7 @@ wallet: password: ${IEXEC_CORE_WALLET_PASSWORD:whatever} chain: - id: ${IEXEC_CHAIN_ID:17} - sidechain: ${IEXEC_IS_SIDECHAIN:false} privateAddress: ${IEXEC_PRIVATE_CHAIN_ADDRESS:http://localhost:8545} - publicAddress: ${IEXEC_PUBLIC_CHAIN_ADDRESS:http://localhost:8545} - hubAddress: ${IEXEC_HUB_ADDRESS:0xBF6B2B07e47326B7c8bfCb4A5460bef9f0Fd2002} poolAddress: ${POOL_ADDRESS:0x365E7BABAa85eC61Dffe5b520763062e6C29dA27} startBlockNumber: ${IEXEC_START_BLOCK_NUMBER:0} gasPriceMultiplier: ${IEXEC_GAS_PRICE_MULTIPLIER:1.0} # txs will be sent with networkGasPrice*gasPriceMultiplier, 4.0 means super fast @@ -67,15 +67,6 @@ resultRepository: host: ${IEXEC_RESULT_REPOSITORY_HOST:localhost} port: ${IEXEC_RESULT_REPOSITORY_PORT:13200} -ipfs: - host: ${IEXEC_IPFS_HOST:127.0.0.1} - port: ${IEXEC_IPFS_PORT:5001} - -sms: - protocol: ${IEXEC_SMS_PROTOCOL:http} - host: ${IEXEC_SMS_HOST:localhost} - port: ${IEXEC_SMS_PORT:13300} - management: endpoints: web: diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt index 9dd0c73d4..2bb249c33 100644 --- a/src/main/resources/banner.txt +++ b/src/main/resources/banner.txt @@ -4,4 +4,4 @@ ${Ansi.YELLOW} \ / | | _| \ \/ / _ \/ __| \ \ \ \ ${Ansi.YELLOW} /_ _\ | | |___ > < __/ (__ / / / / ${Ansi.YELLOW} \/ |_|_____/_/\_\___|\___| /_/_/_/ ${Ansi.YELLOW} ========= -${Ansi.YELLOW} :: Spring Boot${spring-boot.formatted-version} :: ${Ansi.DEFAULT} \ No newline at end of file +${Ansi.YELLOW} :: ${application.title}${application.formatted-version} built with Spring Boot${spring-boot.formatted-version} :: ${Ansi.DEFAULT} \ No newline at end of file diff --git a/src/test/java/com/iexec/core/chain/SignatureServiceTests.java b/src/test/java/com/iexec/core/chain/SignatureServiceTests.java index 509a543b3..08b91b57a 100644 --- a/src/test/java/com/iexec/core/chain/SignatureServiceTests.java +++ b/src/test/java/com/iexec/core/chain/SignatureServiceTests.java @@ -20,8 +20,6 @@ import com.iexec.common.security.Signature; import com.iexec.common.utils.BytesUtils; import com.iexec.common.utils.HashUtils; -import com.iexec.core.configuration.SmsConfiguration; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,7 +34,6 @@ class SignatureServiceTests { @Mock private CredentialsService credentialsService; - @Mock private SmsConfiguration smsConfiguration; @InjectMocks private SignatureService signatureService; diff --git a/src/test/java/com/iexec/core/chain/adapter/BlockchainAdapterServiceTests.java b/src/test/java/com/iexec/core/chain/adapter/BlockchainAdapterServiceTests.java index b8589f096..9ef110344 100644 --- a/src/test/java/com/iexec/core/chain/adapter/BlockchainAdapterServiceTests.java +++ b/src/test/java/com/iexec/core/chain/adapter/BlockchainAdapterServiceTests.java @@ -139,7 +139,7 @@ void isCommandCompletedWithSuccess() { Optional commandCompleted = blockchainAdapterService .isCommandCompleted(blockchainAdapterClient::getStatusForInitializeTaskRequest, CHAIN_TASK_ID, PERIOD, MAX_ATTEMPTS); - Assertions.assertThat(commandCompleted.isPresent()).isTrue(); + Assertions.assertThat(commandCompleted).isPresent(); Assertions.assertThat(commandCompleted.get()).isTrue(); } @@ -153,7 +153,7 @@ void isCommandCompletedWithFailure() { Optional commandCompleted = blockchainAdapterService .isCommandCompleted(blockchainAdapterClient::getStatusForInitializeTaskRequest, CHAIN_TASK_ID, PERIOD, MAX_ATTEMPTS); - Assertions.assertThat(commandCompleted.isPresent()).isTrue(); + Assertions.assertThat(commandCompleted).isPresent(); Assertions.assertThat(commandCompleted.get()).isFalse(); } @@ -170,7 +170,7 @@ void isCommandCompletedWithMaxAttempts() { // region getPublicChainConfig @Test void shouldGetPublicChainConfigOnlyOnce() { - final PublicChainConfig expectedChainConfig = new PublicChainConfig(); + final PublicChainConfig expectedChainConfig = PublicChainConfig.builder().build(); when(blockchainAdapterClient.getPublicChainConfig()) .thenReturn(expectedChainConfig); diff --git a/src/test/java/com/iexec/core/configuration/ConfigurationRepositoryMigrationTest.java b/src/test/java/com/iexec/core/configuration/ConfigurationRepositoryMigrationTest.java index f31dad808..d8ac5ee0f 100644 --- a/src/test/java/com/iexec/core/configuration/ConfigurationRepositoryMigrationTest.java +++ b/src/test/java/com/iexec/core/configuration/ConfigurationRepositoryMigrationTest.java @@ -21,18 +21,26 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import org.bson.Document; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Assertions; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class ConfigurationRepositoryMigrationTest { + @Mock + private MongoCollection collection; + + @Mock + private MongoDatabase db; + + @Mock + private FindIterable findIterable; + @Mock private MongockTemplate mongockTemplate; @@ -58,11 +66,8 @@ void shouldMoveFromReplayField() { } private void mockFindFirstConfiguration(Document document) { - MongoDatabase db = mock(MongoDatabase.class); when(mongockTemplate.getDb()).thenReturn(db); - MongoCollection collection = mock(MongoCollection.class); when(db.getCollection(anyString())).thenReturn(collection); - FindIterable findIterable = mock(FindIterable.class); when(collection.find()).thenReturn(findIterable); when(findIterable.first()).thenReturn(document); } diff --git a/src/test/java/com/iexec/core/configuration/PublicConfigurationServiceTests.java b/src/test/java/com/iexec/core/configuration/PublicConfigurationServiceTests.java index 06985be83..349da0706 100644 --- a/src/test/java/com/iexec/core/configuration/PublicConfigurationServiceTests.java +++ b/src/test/java/com/iexec/core/configuration/PublicConfigurationServiceTests.java @@ -26,8 +26,6 @@ import org.mockito.MockitoAnnotations; import org.web3j.crypto.Credentials; -import java.security.NoSuchAlgorithmException; - import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; @@ -43,8 +41,6 @@ class PublicConfigurationServiceTests { @Mock private ResultRepositoryConfiguration resultRepoConfig; @Mock - private SmsConfiguration smsConfiguration; - @Mock private BlockchainAdapterClientConfig blockchainAdapterClientConfig; @InjectMocks @@ -58,7 +54,7 @@ void init() { // region getPublicConfiguration @Test - void shouldGetPublicConfiguration() throws NoSuchAlgorithmException { + void shouldGetPublicConfiguration() { // This would be done by Spring in production publicConfigurationService.buildPublicConfiguration(); @@ -73,7 +69,7 @@ void shouldNotGetPublicConfigurationWhenNotInitialized() { // region getPublicConfiguration @Test - void shouldGetPublicConfigurationHash() throws NoSuchAlgorithmException { + void shouldGetPublicConfigurationHash() { // This would be done by Spring in production publicConfigurationService.buildPublicConfiguration(); diff --git a/src/test/java/com/iexec/core/configuration/PurgeConfigurationTests.java b/src/test/java/com/iexec/core/configuration/PurgeConfigurationTests.java new file mode 100644 index 000000000..48efede64 --- /dev/null +++ b/src/test/java/com/iexec/core/configuration/PurgeConfigurationTests.java @@ -0,0 +1,25 @@ +package com.iexec.core.configuration; + +import com.iexec.common.lifecycle.purge.PurgeService; +import com.iexec.common.lifecycle.purge.Purgeable; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +class PurgeConfigurationTests { + final PurgeConfiguration purgeConfiguration = new PurgeConfiguration(); + + @Test + void createPurgeService() { + final List purgeables = List.of( + mock(Purgeable.class), + mock(Purgeable.class), + mock(Purgeable.class) + ); + final PurgeService purgeService = purgeConfiguration.purgeService(purgeables); + assertNotNull(purgeService); + } +} \ No newline at end of file diff --git a/src/test/java/com/iexec/core/contribution/ContributionHelperTests.java b/src/test/java/com/iexec/core/contribution/ContributionHelperTests.java index 96919d0a9..d052da821 100644 --- a/src/test/java/com/iexec/core/contribution/ContributionHelperTests.java +++ b/src/test/java/com/iexec/core/contribution/ContributionHelperTests.java @@ -38,17 +38,17 @@ class ContributionHelperTests { @Test void shouldGetContributedWeight() { Replicate replicate1 = mock(Replicate.class); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate1.getContributionHash()).thenReturn(A); when(replicate1.getWorkerWeight()).thenReturn(3); Replicate replicate2 = mock(Replicate.class); - when(replicate2.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate2.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate2.getContributionHash()).thenReturn(A); when(replicate2.getWorkerWeight()).thenReturn(5); Replicate replicate3 = mock(Replicate.class); - when(replicate3.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate3.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate3.getContributionHash()).thenReturn(B); when(replicate3.getWorkerWeight()).thenReturn(10); @@ -61,7 +61,7 @@ void shouldGetContributedWeight() { @Test void shouldNotGetContributedWeightSinceNoContribution() { Replicate replicate1 = mock(Replicate.class); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate1.getContributionHash()).thenReturn(""); when(replicate1.getWorkerWeight()).thenReturn(3); @@ -73,7 +73,7 @@ void shouldNotGetContributedWeightSinceNoContribution() { @Test void shouldNotGetContributedWeightSinceNoWeight() { Replicate replicate1 = mock(Replicate.class); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate1.getContributionHash()).thenReturn(A); when(replicate1.getWorkerWeight()).thenReturn(0); @@ -85,7 +85,7 @@ void shouldNotGetContributedWeightSinceNoWeight() { @Test void shouldNotGetContributedWeightSinceNoTContributed() { Replicate replicate1 = mock(Replicate.class); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTING)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTING); when(replicate1.getContributionHash()).thenReturn(A); when(replicate1.getWorkerWeight()).thenReturn(3); @@ -98,12 +98,12 @@ void shouldNotGetContributedWeightSinceNoTContributed() { void shouldGetPendingWeight() { Replicate replicate1 = mock(Replicate.class); when(replicate1.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); when(replicate1.getWorkerWeight()).thenReturn(3); Replicate replicate2 = mock(Replicate.class); when(replicate2.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate2.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate2.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); when(replicate2.getWorkerWeight()).thenReturn(5); List replicates = Arrays.asList(replicate1, replicate2); @@ -115,7 +115,7 @@ void shouldGetPendingWeight() { void shouldCount0PendingWeightSinceContributed() { Replicate replicate1 = mock(Replicate.class); when(replicate1.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate1.getWorkerWeight()).thenReturn(3); List replicates = Collections.singletonList(replicate1); @@ -127,12 +127,12 @@ void shouldCount0PendingWeightSinceContributed() { void shouldCountOnlyPendingWeightForOneSinceOtherFailed() { Replicate replicate1 = mock(Replicate.class); when(replicate1.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); when(replicate1.getWorkerWeight()).thenReturn(3); Replicate replicate2 = mock(Replicate.class); when(replicate2.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate2.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.FAILED)); + when(replicate2.getLastRelevantStatus()).thenReturn(ReplicateStatus.FAILED); when(replicate2.getWorkerWeight()).thenReturn(5); List replicates = Arrays.asList(replicate1, replicate2); @@ -144,12 +144,12 @@ void shouldCountOnlyPendingWeightForOneSinceOtherFailed() { void shouldCountOnlyPendingWeightForOneSinceOtherContributed() { Replicate replicate1 = mock(Replicate.class); when(replicate1.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); when(replicate1.getWorkerWeight()).thenReturn(3); Replicate replicate2 = mock(Replicate.class); when(replicate2.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate2.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate2.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate2.getWorkerWeight()).thenReturn(5); List replicates = Arrays.asList(replicate1, replicate2); @@ -161,12 +161,12 @@ void shouldCountOnlyPendingWeightForOneSinceOtherContributed() { void shouldCountOnlyPendingWeightForOneSinceOtherVeryOld() { Replicate replicate1 = mock(Replicate.class); when(replicate1.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); when(replicate1.getWorkerWeight()).thenReturn(3); Replicate replicate2 = mock(Replicate.class); when(replicate2.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(true); - when(replicate2.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate2.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); when(replicate2.getWorkerWeight()).thenReturn(5); List replicates = Arrays.asList(replicate1, replicate2); @@ -178,12 +178,12 @@ void shouldCountOnlyPendingWeightForOneSinceOtherVeryOld() { void shouldCountOnlyPendingWeightForOneSinceOtherNoWeight() { Replicate replicate1 = mock(Replicate.class); when(replicate1.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); when(replicate1.getWorkerWeight()).thenReturn(3); Replicate replicate2 = mock(Replicate.class); when(replicate2.isCreatedMoreThanNPeriodsAgo(anyInt(), anyLong())).thenReturn(false); - when(replicate2.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate2.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); when(replicate2.getWorkerWeight()).thenReturn(0); List replicates = Arrays.asList(replicate1, replicate2); @@ -194,17 +194,17 @@ void shouldCountOnlyPendingWeightForOneSinceOtherNoWeight() { @Test void shouldGetDistinctContributions() { Replicate replicate1 = mock(Replicate.class); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate1.getContributionHash()).thenReturn(A); when(replicate1.getWorkerWeight()).thenReturn(3); Replicate replicate2 = mock(Replicate.class); - when(replicate2.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate2.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate2.getContributionHash()).thenReturn(A); when(replicate2.getWorkerWeight()).thenReturn(5); Replicate replicate3 = mock(Replicate.class); - when(replicate3.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CONTRIBUTED)); + when(replicate3.getLastRelevantStatus()).thenReturn(ReplicateStatus.CONTRIBUTED); when(replicate3.getContributionHash()).thenReturn(B); when(replicate3.getWorkerWeight()).thenReturn(10); @@ -217,7 +217,7 @@ void shouldGetDistinctContributions() { @Test void shouldNotGetDistinctContributionsSinceNotContributed() { Replicate replicate1 = mock(Replicate.class); - when(replicate1.getLastRelevantStatus()).thenReturn(Optional.of(ReplicateStatus.CREATED)); + when(replicate1.getLastRelevantStatus()).thenReturn(ReplicateStatus.CREATED); List replicates = Collections.singletonList(replicate1); diff --git a/src/test/java/com/iexec/core/detector/task/FinalDeadlineTaskDetectorTests.java b/src/test/java/com/iexec/core/detector/task/FinalDeadlineTaskDetectorTests.java index e45f5ffce..ee19afa7f 100644 --- a/src/test/java/com/iexec/core/detector/task/FinalDeadlineTaskDetectorTests.java +++ b/src/test/java/com/iexec/core/detector/task/FinalDeadlineTaskDetectorTests.java @@ -38,8 +38,6 @@ class FinalDeadlineTaskDetectorTests { - private final static String CHAIN_TASK_ID = "chainTaskId"; - @Mock private TaskService taskService; diff --git a/src/test/java/com/iexec/core/registry/PlatformRegistryConfigurationTests.java b/src/test/java/com/iexec/core/registry/PlatformRegistryConfigurationTests.java new file mode 100644 index 000000000..4945aaa6c --- /dev/null +++ b/src/test/java/com/iexec/core/registry/PlatformRegistryConfigurationTests.java @@ -0,0 +1,30 @@ +package com.iexec.core.registry; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = PlatformRegistryConfiguration.class) +@TestPropertySource(properties = { + "sms.scone=http://scone-sms", + "sms.gramine=http://gramine-sms" + }) +class PlatformRegistryConfigurationTests { + + @Autowired + PlatformRegistryConfiguration platformRegistryConfiguration; + + @Test + void shouldGetValues() { + Assertions.assertThat(platformRegistryConfiguration.getSconeSms()) + .isEqualTo("http://scone-sms"); + Assertions.assertThat(platformRegistryConfiguration.getGramineSms()) + .isEqualTo("http://gramine-sms"); + } + +} diff --git a/src/test/java/com/iexec/core/replicate/ReplicateControllerTests.java b/src/test/java/com/iexec/core/replicate/ReplicateControllerTests.java index 34f3a0fe7..1644b51a0 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateControllerTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateControllerTests.java @@ -24,12 +24,17 @@ class ReplicateControllerTests { private static final String CHAIN_TASK_ID = "chainTaskId"; private static final String WALLET_ADDRESS = "walletAddress"; + private static final String SMS_URL = "smsUrl"; private static final String TOKEN = "token"; private static final int BLOCK_NUMBER = 1; private static final WorkerpoolAuthorization AUTH = WorkerpoolAuthorization.builder() .chainTaskId(CHAIN_TASK_ID) .workerWallet(WALLET_ADDRESS) .build(); + private static final ReplicateTaskSummary REPLICATE_TASK_SUMMARY = ReplicateTaskSummary.builder() + .workerpoolAuthorization(AUTH) + .smsUrl(SMS_URL) + .build(); private static final ReplicateStatusUpdate UPDATE = ReplicateStatusUpdate.builder() .status(ReplicateStatus.STARTED) .build(); @@ -63,15 +68,15 @@ void shouldGetAvailableReplicate() { when(workerService.isWorkerAllowedToAskReplicate(WALLET_ADDRESS)) .thenReturn(true); when(replicateSupplyService - .getAuthOfAvailableReplicate(BLOCK_NUMBER, WALLET_ADDRESS)) - .thenReturn(Optional.of(AUTH)); + .getAvailableReplicateTaskSummary(BLOCK_NUMBER, WALLET_ADDRESS)) + .thenReturn(Optional.of(REPLICATE_TASK_SUMMARY)); - ResponseEntity response = - replicatesController.getAvailableReplicate(BLOCK_NUMBER, TOKEN); + ResponseEntity replicateTaskSummaryResponse = + replicatesController.getAvailableReplicateTaskSummary(BLOCK_NUMBER, TOKEN); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - WorkerpoolAuthorization auth = response.getBody(); - assertThat(auth.getChainTaskId()).isEqualTo(CHAIN_TASK_ID); + assertThat(replicateTaskSummaryResponse.getStatusCode()).isEqualTo(HttpStatus.OK); + ReplicateTaskSummary replicateTaskSummary = replicateTaskSummaryResponse.getBody(); + assertThat(replicateTaskSummary.getWorkerpoolAuthorization().getChainTaskId()).isEqualTo(CHAIN_TASK_ID); } @Test @@ -79,10 +84,10 @@ void shouldNotGetAvailableReplicateSinceNotAuthorizedToken() { when(jwtTokenProvider.getWalletAddressFromBearerToken(TOKEN)) .thenReturn(""); - ResponseEntity response = - replicatesController.getAvailableReplicate(BLOCK_NUMBER, TOKEN); + ResponseEntity replicateTaskSummaryResponse = + replicatesController.getAvailableReplicateTaskSummary(BLOCK_NUMBER, TOKEN); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + assertThat(replicateTaskSummaryResponse.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); } @Test @@ -92,10 +97,10 @@ void shouldNotGetAvailableReplicateSinceNotAllowed() { when(workerService.isWorkerAllowedToAskReplicate(WALLET_ADDRESS)) .thenReturn(false); - ResponseEntity response = - replicatesController.getAvailableReplicate(BLOCK_NUMBER, TOKEN); + ResponseEntity replicateTaskSummaryResponse = + replicatesController.getAvailableReplicateTaskSummary(BLOCK_NUMBER, TOKEN); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + assertThat(replicateTaskSummaryResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); } @Test @@ -105,13 +110,13 @@ void shouldNotGetAvailableReplicateSinceNoReplicateAvailable() { when(workerService.isWorkerAllowedToAskReplicate(WALLET_ADDRESS)) .thenReturn(true); when(replicateSupplyService - .getAuthOfAvailableReplicate(BLOCK_NUMBER, WALLET_ADDRESS)) + .getAvailableReplicateTaskSummary(BLOCK_NUMBER, WALLET_ADDRESS)) .thenReturn(Optional.empty()); - ResponseEntity response = - replicatesController.getAvailableReplicate(BLOCK_NUMBER, TOKEN); + ResponseEntity replicateTaskSummaryResponse = + replicatesController.getAvailableReplicateTaskSummary(BLOCK_NUMBER, TOKEN); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); + assertThat(replicateTaskSummaryResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); } //endregion @@ -164,7 +169,6 @@ void shouldGetEmptyMissedNotifications() { //endregion //region update replicate - @Test void shouldUpdateReplicate() { when(jwtTokenProvider.getWalletAddressFromBearerToken(TOKEN)) @@ -220,6 +224,7 @@ void shouldNotUpdateReplicateSinceUnauthorized() { replicatesController.updateReplicateStatus(TOKEN, CHAIN_TASK_ID, UPDATE); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + assertThat(response.getBody()).isNull(); } @Test @@ -235,6 +240,7 @@ void shouldNotUpdateReplicateSinceForbidden() { replicatesController.updateReplicateStatus(TOKEN, CHAIN_TASK_ID, UPDATE); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); + assertThat(response.getBody()).isEqualTo(TaskNotificationType.PLEASE_ABORT); } @Test diff --git a/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java b/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java index e13bcc3ac..58115905a 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateServiceTests.java @@ -140,7 +140,7 @@ void shouldGetReplicates() { when(replicatesRepository.findByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); assertThat(replicatesService.getReplicates(CHAIN_TASK_ID)).isNotNull(); - assertThat(replicatesService.getReplicates(CHAIN_TASK_ID).size()).isEqualTo(1); + assertThat(replicatesService.getReplicates(CHAIN_TASK_ID)).hasSize(1); } @Test @@ -167,7 +167,7 @@ void shouldGetReplicate() { @Test void shouldNotGetReplicate1() { when(replicatesRepository.findByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.empty()); - assertThat(replicatesService.getReplicate(CHAIN_TASK_ID, WALLET_WORKER_1)).isEqualTo(Optional.empty()); + assertThat(replicatesService.getReplicate(CHAIN_TASK_ID, WALLET_WORKER_1)).isEmpty(); } @Test @@ -181,7 +181,7 @@ void shouldNotGetReplicate2() { ReplicatesList replicatesList = new ReplicatesList(CHAIN_TASK_ID, Arrays.asList(replicate1, replicate2)); when(replicatesRepository.findByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); - assertThat(replicatesService.getReplicate(CHAIN_TASK_ID, WALLET_WORKER_3)).isEqualTo(Optional.empty()); + assertThat(replicatesService.getReplicate(CHAIN_TASK_ID, WALLET_WORKER_3)).isEmpty(); } @Test @@ -200,7 +200,7 @@ void shouldGetCorrectNbReplicatesWithOneStatus() { assertThat(replicatesService.getNbReplicatesWithCurrentStatus(CHAIN_TASK_ID, STARTING)).isEqualTo(2); assertThat(replicatesService.getNbReplicatesWithCurrentStatus(CHAIN_TASK_ID, COMPUTED)).isEqualTo(1); - assertThat(replicatesService.getNbReplicatesWithCurrentStatus(CHAIN_TASK_ID, CONTRIBUTED)).isEqualTo(0); + assertThat(replicatesService.getNbReplicatesWithCurrentStatus(CHAIN_TASK_ID, CONTRIBUTED)).isZero(); } @Test @@ -252,7 +252,7 @@ void shouldGetCorrectNbReplicatesWithOneLastRelevantStatus() { assertThat(replicatesService.getNbReplicatesWithLastRelevantStatus(CHAIN_TASK_ID, STARTING)).isEqualTo(3); assertThat(replicatesService.getNbReplicatesWithLastRelevantStatus(CHAIN_TASK_ID, COMPUTED)).isEqualTo(1); - assertThat(replicatesService.getNbReplicatesWithLastRelevantStatus(CHAIN_TASK_ID, CONTRIBUTED)).isEqualTo(0); + assertThat(replicatesService.getNbReplicatesWithLastRelevantStatus(CHAIN_TASK_ID, CONTRIBUTED)).isZero(); } @Test @@ -304,7 +304,7 @@ void shouldGetCorrectNbReplicatesContainingOneStatus() { assertThat(replicatesService.getNbReplicatesContainingStatus(CHAIN_TASK_ID, STARTING)).isEqualTo(3); assertThat(replicatesService.getNbReplicatesContainingStatus(CHAIN_TASK_ID, COMPUTED)).isEqualTo(1); - assertThat(replicatesService.getNbReplicatesContainingStatus(CHAIN_TASK_ID, CONTRIBUTED)).isEqualTo(0); + assertThat(replicatesService.getNbReplicatesContainingStatus(CHAIN_TASK_ID, CONTRIBUTED)).isZero(); } @Test @@ -333,7 +333,7 @@ void shouldGetCorrectNbReplicatesContainingMultipleStatus() { int shouldBe0 = replicatesService.getNbReplicatesContainingStatus(CHAIN_TASK_ID, COMPLETED, FAILED, RESULT_UPLOADING); - assertThat(shouldBe0).isEqualTo(0); + assertThat(shouldBe0).isZero(); } @Test @@ -354,8 +354,7 @@ void shouldGetReplicateWithRevealStatus() { when(replicatesRepository.findByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); Optional optional = replicatesService.getRandomReplicateWithRevealStatus(CHAIN_TASK_ID); - assertThat(optional.isPresent()).isTrue(); - assertThat(optional).isEqualTo(Optional.of(replicate)); + assertThat(optional).contains(replicate); } @Test @@ -363,7 +362,7 @@ void shouldNotGetReplicateWithRevealStatusSinceEmptyReplicatesList() { when(replicatesRepository.findByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.empty()); Optional optional = replicatesService.getRandomReplicateWithRevealStatus(CHAIN_TASK_ID); - assertThat(optional.isPresent()).isFalse(); + assertThat(optional).isEmpty(); } @Test @@ -383,7 +382,7 @@ void shouldNotGetReplicateWithRevealStatusWithNonEmptyList() { when(replicatesRepository.findByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); Optional optional = replicatesService.getRandomReplicateWithRevealStatus(CHAIN_TASK_ID); - assertThat(optional.isPresent()).isFalse(); + assertThat(optional).isEmpty(); } @Test @@ -629,8 +628,7 @@ void shouldNotUpdateReplicateStatusSinceAlreadyReported() { .build(); final Optional result = replicatesService.updateReplicateStatus(CHAIN_TASK_ID, WALLET_WORKER_1, statusUpdate); - assertThat(result) - .isEqualTo(Optional.empty()); + assertThat(result).isEmpty(); } @Test diff --git a/src/test/java/com/iexec/core/replicate/ReplicateSupplyServiceTests.java b/src/test/java/com/iexec/core/replicate/ReplicateSupplyServiceTests.java index 17655ddd7..208ab1358 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateSupplyServiceTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateSupplyServiceTests.java @@ -20,11 +20,9 @@ import com.iexec.common.notification.TaskNotification; import com.iexec.common.notification.TaskNotificationExtra; import com.iexec.common.notification.TaskNotificationType; -import com.iexec.common.replicate.ReplicateStatus; -import com.iexec.common.replicate.ReplicateStatusDetails; -import com.iexec.common.replicate.ReplicateStatusModifier; -import com.iexec.common.replicate.ReplicateStatusUpdate; +import com.iexec.common.replicate.*; import com.iexec.common.task.TaskAbortCause; +import com.iexec.common.tee.TeeUtils; import com.iexec.common.utils.BytesUtils; import com.iexec.common.utils.DateTimeUtils; import com.iexec.core.chain.SignatureService; @@ -35,10 +33,10 @@ import com.iexec.core.task.update.TaskUpdateRequestManager; import com.iexec.core.worker.Worker; import com.iexec.core.worker.WorkerService; -import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.*; +import org.springframework.test.util.ReflectionTestUtils; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -49,12 +47,12 @@ import static com.iexec.common.replicate.ReplicateStatus.*; import static com.iexec.core.task.TaskStatus.RUNNING; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; -@Slf4j class ReplicateSupplyServiceTests { private final static String WALLET_WORKER_1 = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; @@ -66,7 +64,7 @@ class ReplicateSupplyServiceTests { private final static String DAPP_NAME = "dappName"; private final static String COMMAND_LINE = "commandLine"; private final static String NO_TEE_TAG = BytesUtils.EMPTY_HEX_STRING_32; - private final static String TEE_TAG = "0x0000000000000000000000000000000000000000000000000000000000000001"; + private final static String TEE_TAG = TeeUtils.TEE_SCONE_ONLY_TAG; //any supported TEE tag private final static String ENCLAVE_CHALLENGE = "dummyEnclave"; private final static long maxExecutionTime = 60000; long workerLastBlock = 12; @@ -100,18 +98,18 @@ void workerCanWorkAndHasGas(String workerAddress) { @Test void shouldNotGetAnyReplicateSinceWorkerDoesNotExist() { when(workerService.getWorker(Mockito.anyString())).thenReturn(Optional.empty()); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verifyNoInteractions(web3jService, taskService, taskUpdateRequestManager, replicatesService, signatureService); } @Test void shouldNotGetReplicateSinceWorkerLastBlockNotAvailable() { workerCanWorkAndHasGas(WALLET_WORKER_1); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(0, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(0, WALLET_WORKER_1); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verifyNoInteractions(web3jService, taskService, taskUpdateRequestManager, replicatesService, signatureService); } @@ -119,9 +117,9 @@ void shouldNotGetReplicateSinceWorkerLastBlockNotAvailable() { void shouldNotGetReplicateSinceNoRunningTask() { workerCanWorkAndHasGas(WALLET_WORKER_1); when(taskService.getPrioritizedInitializedOrRunningTask(false, Collections.emptyList())).thenReturn(Optional.empty()); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verify(taskService, Mockito.never()).getTaskByChainTaskId(CHAIN_TASK_ID); Mockito.verifyNoInteractions(taskUpdateRequestManager, replicatesService, signatureService); } @@ -149,9 +147,9 @@ void shouldNotGetReplicateSinceNoReplicatesList() { when(workerService.getWorker(WALLET_WORKER_2)).thenReturn(Optional.of(worker)); when(replicatesService.getReplicatesList(CHAIN_TASK_ID)).thenReturn(Optional.empty()); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_2); - assertThat(oAuthorization).isEmpty(); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_2); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verify(taskService, Mockito.never()).isConsensusReached(any()); Mockito.verifyNoInteractions(signatureService); assertTaskAccessForNewReplicateLockNeverUsed(CHAIN_TASK_ID); @@ -188,10 +186,10 @@ void shouldNotGetReplicateSinceConsensusReachedOnChain() { when(taskService.isConsensusReached(replicatesList)).thenReturn(true); when(replicatesList.hasWorkerAlreadyParticipated(WALLET_WORKER_2)).thenReturn(false); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_2); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_2); - assertThat(oAuthorization).isEmpty(); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verify(taskService).isConsensusReached(replicatesList); Mockito.verifyNoInteractions(signatureService); @@ -201,9 +199,9 @@ void shouldNotGetReplicateSinceConsensusReachedOnChain() { @Test void shouldNotGetAnyReplicateSinceWorkerIsFull() { when(workerService.canAcceptMoreWorks(WALLET_WORKER_1)).thenReturn(false); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verifyNoInteractions(taskService, taskUpdateRequestManager, replicatesService, signatureService); } @@ -211,9 +209,9 @@ void shouldNotGetAnyReplicateSinceWorkerIsFull() { void shouldNotGetAnyReplicateSinceWorkerDoesNotHaveEnoughGas() { when(workerService.canAcceptMoreWorks(WALLET_WORKER_1)).thenReturn(true); when(web3jService.hasEnoughGas(WALLET_WORKER_1)).thenReturn(false); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verify(web3jService).hasEnoughGas(WALLET_WORKER_1); Mockito.verifyNoInteractions(taskService, taskUpdateRequestManager, replicatesService, signatureService); } @@ -246,10 +244,10 @@ void shouldNotGetAnyReplicateSinceWorkerAlreadyParticipated() { when(replicatesService.getReplicatesList(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); when(replicatesList.hasWorkerAlreadyParticipated(WALLET_WORKER_1)).thenReturn(true); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verifyNoInteractions(signatureService); assertTaskAccessForNewReplicateLockNeverUsed(CHAIN_TASK_ID); @@ -289,9 +287,9 @@ void shouldNotGetReplicateSinceDoesNotNeedMoreContributionsForConsensus() { when(workerService.getWorker(WALLET_WORKER_2)).thenReturn(Optional.of(existingWorker)); when(replicatesService.getReplicatesList(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); when(replicatesList.hasWorkerAlreadyParticipated(WALLET_WORKER_1)).thenReturn(false); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_2); - assertThat(oAuthorization).isEmpty(); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_2); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verifyNoInteractions(signatureService); assertTaskAccessForNewReplicateNotDeadLocking(CHAIN_TASK_ID); @@ -325,10 +323,10 @@ void shouldNotGetReplicateSinceEnclaveChallengeNeededButNotGenerated() { when(replicatesService.getReplicatesList(CHAIN_TASK_ID)).thenReturn(Optional.of(replicatesList)); when(replicatesList.hasWorkerAlreadyParticipated(WALLET_WORKER_1)).thenReturn(false); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verify(replicatesService, Mockito.never()).addNewReplicate(CHAIN_TASK_ID, WALLET_WORKER_1); Mockito.verify(workerService, Mockito.never()).addChainTaskIdToWorker(CHAIN_TASK_ID, WALLET_WORKER_1); @@ -373,10 +371,10 @@ void shouldGetOnlyOneReplicateSinceOtherOneReachedConsensusDeadline() { when(signatureService.createAuthorization(WALLET_WORKER_1, CHAIN_TASK_ID, BytesUtils.EMPTY_ADDRESS)) .thenReturn(WorkerpoolAuthorization.builder().chainTaskId(CHAIN_TASK_ID).build()); - final Optional oAuthorization = replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); + final Optional replicateTaskSummary = replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isPresent(); - assertThat(oAuthorization.get().getChainTaskId()).isEqualTo(CHAIN_TASK_ID); + assertThat(replicateTaskSummary).isPresent(); + assertThat(replicateTaskSummary.get().getWorkerpoolAuthorization().getChainTaskId()).isEqualTo(CHAIN_TASK_ID); Mockito.verify(replicatesService).addNewReplicate(CHAIN_TASK_ID, WALLET_WORKER_1); Mockito.verify(workerService).addChainTaskIdToWorker(CHAIN_TASK_ID, WALLET_WORKER_1); @@ -409,10 +407,10 @@ void shouldNotGetReplicateWhenTaskAlreadyAccessed() { final Lock lock = replicateSupplyService.taskAccessForNewReplicateLocks.computeIfAbsent(CHAIN_TASK_ID, k -> new ReentrantLock()); CompletableFuture.runAsync(lock::lock).join(); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verifyNoInteractions(signatureService); } @@ -446,9 +444,9 @@ void shouldGetReplicateWithNoTee() { .thenReturn(new WorkerpoolAuthorization()); when(replicatesList.hasWorkerAlreadyParticipated(WALLET_WORKER_1)).thenReturn(false); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isPresent(); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); + assertThat(replicateTaskSummary).isPresent(); Mockito.verify(replicatesService).addNewReplicate(CHAIN_TASK_ID, WALLET_WORKER_1); Mockito.verify(workerService).addChainTaskIdToWorker(CHAIN_TASK_ID, WALLET_WORKER_1); @@ -486,10 +484,10 @@ void shouldGetReplicateWithTee() { .thenReturn(new WorkerpoolAuthorization()); when(replicatesList.hasWorkerAlreadyParticipated(WALLET_WORKER_1)).thenReturn(false); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isPresent(); + assertThat(replicateTaskSummary).isPresent(); Mockito.verify(replicatesService).addNewReplicate(CHAIN_TASK_ID, WALLET_WORKER_1); Mockito.verify(workerService).addChainTaskIdToWorker(CHAIN_TASK_ID, WALLET_WORKER_1); @@ -517,10 +515,10 @@ void shouldTeeNeededTaskNotBeGivenToTeeDisabledWorker() { .thenReturn(Optional.empty()); when(workerService.getWorker(WALLET_WORKER_1)).thenReturn(Optional.of(existingWorker)); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isEmpty(); + assertThat(replicateTaskSummary).isEmpty(); Mockito.verifyNoInteractions(signatureService); assertTaskAccessForNewReplicateLockNeverUsed(CHAIN_TASK_ID); } @@ -555,10 +553,10 @@ void shouldTeeNeededTaskBeGivenToTeeEnabledWorker() { .thenReturn(new WorkerpoolAuthorization()); when(replicatesList.hasWorkerAlreadyParticipated(WALLET_WORKER_1)).thenReturn(false); - Optional oAuthorization = - replicateSupplyService.getAuthOfAvailableReplicate(workerLastBlock, WALLET_WORKER_1); + Optional replicateTaskSummary = + replicateSupplyService.getAvailableReplicateTaskSummary(workerLastBlock, WALLET_WORKER_1); - assertThat(oAuthorization).isPresent(); + assertThat(replicateTaskSummary).isPresent(); Mockito.verify(replicatesService).addNewReplicate(CHAIN_TASK_ID, WALLET_WORKER_1); Mockito.verify(workerService).addChainTaskIdToWorker(CHAIN_TASK_ID, WALLET_WORKER_1); @@ -1156,6 +1154,59 @@ void shouldNotTellReplicateToWaitForCompletionSinceItDidNotReveal() { .updateReplicateStatus(CHAIN_TASK_ID, WALLET_WORKER_1, RECOVERING); } + // region purgeTask + @Test + void shouldPurgeTaskWhenKnownTask() { + final Map taskAccessForNewReplicateLocks = new HashMap<>(); + taskAccessForNewReplicateLocks.put(CHAIN_TASK_ID, new ReentrantLock()); + ReflectionTestUtils.setField(replicateSupplyService, "taskAccessForNewReplicateLocks", taskAccessForNewReplicateLocks); + + assertTrue(replicateSupplyService.purgeTask(CHAIN_TASK_ID)); + assertThat(taskAccessForNewReplicateLocks).isEmpty(); + } + + @Test + void shouldPurgeTaskWhenUnknownTask() { + final Map taskAccessForNewReplicateLocks = new HashMap<>(); + taskAccessForNewReplicateLocks.put(CHAIN_TASK_ID_2, new ReentrantLock()); + ReflectionTestUtils.setField(replicateSupplyService, "taskAccessForNewReplicateLocks", taskAccessForNewReplicateLocks); + + assertTrue(replicateSupplyService.purgeTask(CHAIN_TASK_ID)); + assertThat(taskAccessForNewReplicateLocks).containsOnlyKeys(CHAIN_TASK_ID_2); + } + + @Test + void shouldPurgeTaskWhenEmpty() { + final Map taskAccessForNewReplicateLocks = new HashMap<>(); + ReflectionTestUtils.setField(replicateSupplyService, "taskAccessForNewReplicateLocks", taskAccessForNewReplicateLocks); + + assertTrue(replicateSupplyService.purgeTask(CHAIN_TASK_ID)); + assertThat(taskAccessForNewReplicateLocks).isEmpty(); + } + // endregion + + // region purgeAllTasksData + @Test + void shouldPurgeAllTasksDataWhenEmpty() { + final Map taskAccessForNewReplicateLocks = new HashMap<>(); + ReflectionTestUtils.setField(replicateSupplyService, "taskAccessForNewReplicateLocks", taskAccessForNewReplicateLocks); + + replicateSupplyService.purgeAllTasksData(); + assertThat(taskAccessForNewReplicateLocks).isEmpty(); + } + + @Test + void shouldPurgeAllTasksDataWhenFull() { + final Map taskAccessForNewReplicateLocks = new HashMap<>(); + taskAccessForNewReplicateLocks.put(CHAIN_TASK_ID, new ReentrantLock()); + taskAccessForNewReplicateLocks.put(CHAIN_TASK_ID_2, new ReentrantLock()); + ReflectionTestUtils.setField(replicateSupplyService, "taskAccessForNewReplicateLocks", taskAccessForNewReplicateLocks); + + replicateSupplyService.purgeAllTasksData(); + assertThat(taskAccessForNewReplicateLocks).isEmpty(); + } + // endregion + List getStubTaskList(TaskStatus status) { Task task = Task.builder() .chainTaskId(CHAIN_TASK_ID) @@ -1166,9 +1217,7 @@ List getStubTaskList(TaskStatus status) { } Optional getStubReplicate(ReplicateStatus status) { - Replicate replicate = new Replicate(); - replicate.setWalletAddress(WALLET_WORKER_1); - replicate.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate.setStatusUpdateList(new ArrayList<>()); replicate.updateStatus(status, ReplicateStatusModifier.WORKER); return Optional.of(replicate); diff --git a/src/test/java/com/iexec/core/replicate/ReplicateTests.java b/src/test/java/com/iexec/core/replicate/ReplicateTests.java index ff999ee65..a2fc7efd1 100644 --- a/src/test/java/com/iexec/core/replicate/ReplicateTests.java +++ b/src/test/java/com/iexec/core/replicate/ReplicateTests.java @@ -22,15 +22,23 @@ import com.iexec.common.replicate.ReplicateStatusModifier; import com.iexec.common.replicate.ReplicateStatusUpdate; import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.concurrent.TimeUnit; +import static com.iexec.common.replicate.ReplicateStatus.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class ReplicateTests { + private static final String WALLET_WORKER = "worker"; + private static final String CHAIN_TASK_ID = "chainTaskId"; + + private final ObjectMapper mapper = new ObjectMapper(); @Test @@ -59,8 +67,8 @@ void shouldSerializeAndDeserializeReplicate() throws JsonProcessingException { @Test void shouldInitializeStatusProperly(){ - Replicate replicate = new Replicate("worker", "taskId"); - assertThat(replicate.getStatusUpdateList().size()).isEqualTo(1); + Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + assertThat(replicate.getStatusUpdateList()).hasSize(1); ReplicateStatusUpdate statusChange = replicate.getStatusUpdateList().get(0); assertThat(statusChange.getStatus()).isEqualTo(ReplicateStatus.CREATED); @@ -73,18 +81,18 @@ void shouldInitializeStatusProperly(){ @Test void shouldUpdateReplicateStatus(){ - Replicate replicate = new Replicate("worker", "taskId"); - assertThat(replicate.getStatusUpdateList().size()).isEqualTo(1); + Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + assertThat(replicate.getStatusUpdateList()).hasSize(1); // only pool manager sets date of the update - replicate.updateStatus(ReplicateStatus.STARTING, ReplicateStatusModifier.POOL_MANAGER); - assertThat(replicate.getStatusUpdateList().size()).isEqualTo(2); + replicate.updateStatus(STARTING, ReplicateStatusModifier.POOL_MANAGER); + assertThat(replicate.getStatusUpdateList()).hasSize(2); ReplicateStatusUpdate initialStatus = replicate.getStatusUpdateList().get(0); assertThat(initialStatus.getStatus()).isEqualTo(ReplicateStatus.CREATED); ReplicateStatusUpdate updatedStatus = replicate.getStatusUpdateList().get(1); - assertThat(updatedStatus.getStatus()).isEqualTo(ReplicateStatus.STARTING); + assertThat(updatedStatus.getStatus()).isEqualTo(STARTING); Date now = new Date(); long duration = now.getTime() - updatedStatus.getDate().getTime(); @@ -94,20 +102,20 @@ void shouldUpdateReplicateStatus(){ @Test void shouldGetProperLatestStatus(){ - Replicate replicate = new Replicate("worker", "taskId"); - assertThat(replicate.getStatusUpdateList().size()).isEqualTo(1); + Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + assertThat(replicate.getStatusUpdateList()).hasSize(1); assertThat(replicate.getCurrentStatus()).isEqualTo(ReplicateStatus.CREATED); - replicate.updateStatus(ReplicateStatus.STARTING, ReplicateStatusModifier.WORKER); - assertThat(replicate.getStatusUpdateList().size()).isEqualTo(2); - assertThat(replicate.getCurrentStatus()).isEqualTo(ReplicateStatus.STARTING); + replicate.updateStatus(STARTING, ReplicateStatusModifier.WORKER); + assertThat(replicate.getStatusUpdateList()).hasSize(2); + assertThat(replicate.getCurrentStatus()).isEqualTo(STARTING); } @Test void shouldReturnTrueWhenContributed(){ - Replicate replicate = new Replicate("0x1", "taskId"); - replicate.updateStatus(ReplicateStatus.STARTING, ReplicateStatusModifier.WORKER); + Replicate replicate = new Replicate("0x1", CHAIN_TASK_ID); + replicate.updateStatus(STARTING, ReplicateStatusModifier.WORKER); replicate.updateStatus(ReplicateStatus.CONTRIBUTING, ReplicateStatusModifier.WORKER); replicate.updateStatus(ReplicateStatus.CONTRIBUTED, ReplicateStatusModifier.WORKER); replicate.updateStatus(ReplicateStatus.REVEALING, ReplicateStatusModifier.WORKER); @@ -118,8 +126,8 @@ void shouldReturnTrueWhenContributed(){ @Test void shouldReturnFalseWhenContributedMissing(){ - Replicate replicate = new Replicate("0x1", "taskId"); - replicate.updateStatus(ReplicateStatus.STARTING, ReplicateStatusModifier.WORKER); + Replicate replicate = new Replicate("0x1", CHAIN_TASK_ID); + replicate.updateStatus(STARTING, ReplicateStatusModifier.WORKER); replicate.updateStatus(ReplicateStatus.CONTRIBUTING, ReplicateStatusModifier.WORKER); replicate.updateStatus(ReplicateStatus.REVEALING, ReplicateStatusModifier.WORKER); replicate.updateStatus(ReplicateStatus.REVEALED, ReplicateStatusModifier.WORKER); @@ -131,7 +139,7 @@ void shouldReturnFalseWhenContributedMissing(){ void shouldBeCreatedLongAgo(){ final long maxExecutionTime = 60000; Date now = new Date(); - Replicate replicate = new Replicate("0x1", "taskId"); + Replicate replicate = new Replicate("0x1", CHAIN_TASK_ID); ReplicateStatusUpdate oldCreationDate = replicate.getStatusUpdateList().get(0); oldCreationDate.setDate(new Date(now.getTime() - 3 * maxExecutionTime)); replicate.setStatusUpdateList(Collections.singletonList(oldCreationDate)); @@ -143,7 +151,7 @@ void shouldBeCreatedLongAgo(){ void shouldNotBeCreatedLongAgo(){ final long maxExecutionTime = 60000; Date now = new Date(); - Replicate replicate = new Replicate("0x1", "taskId"); + Replicate replicate = new Replicate("0x1", CHAIN_TASK_ID); ReplicateStatusUpdate oldCreationDate = replicate.getStatusUpdateList().get(0); oldCreationDate.setDate(new Date(now.getTime() - maxExecutionTime)); replicate.setStatusUpdateList(Collections.singletonList(oldCreationDate)); @@ -153,9 +161,9 @@ void shouldNotBeCreatedLongAgo(){ @Test void shouldBeBusyComputing() { - Replicate replicate = new Replicate("worker", "taskId"); + Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); assertThat(replicate.isBusyComputing()).isTrue(); - replicate.updateStatus(ReplicateStatus.STARTING, ReplicateStatusModifier.WORKER); + replicate.updateStatus(STARTING, ReplicateStatusModifier.WORKER); assertThat(replicate.isBusyComputing()).isTrue(); replicate.updateStatus(ReplicateStatus.APP_DOWNLOADING, ReplicateStatusModifier.WORKER); assertThat(replicate.isBusyComputing()).isTrue(); @@ -177,4 +185,72 @@ void shouldBeBusyComputing() { replicate.updateStatus(ReplicateStatus.COMPLETED, ReplicateStatusModifier.WORKER); assertThat(replicate.isBusyComputing()).isFalse(); } + + // region getLastRelevantStatus + @Test + void shouldGetLastRelevantStatusWhenOnlyRelevantStatus() { + final Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + replicate.updateStatus(STARTING, ReplicateStatusModifier.WORKER); + replicate.updateStatus(STARTED, ReplicateStatusModifier.WORKER); + + assertThat(replicate.getLastRelevantStatus()).isEqualTo(STARTED); + } + + @Test + void shouldGetLastRelevantStatusWhenRelevantAndIrrelevantStatus() { + final Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + replicate.updateStatus(STARTING, ReplicateStatusModifier.WORKER); + replicate.updateStatus(STARTED, ReplicateStatusModifier.WORKER); + replicate.updateStatus(WORKER_LOST, ReplicateStatusModifier.WORKER); + + assertThat(replicate.getLastRelevantStatus()).isEqualTo(STARTED); + } + + @Test + void shouldNotGetLastRelevantStatusWhenNoStatusAtAll() { + final Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + ReflectionTestUtils.setField(replicate, "statusUpdateList", List.of()); + + assertThatExceptionOfType(NoReplicateStatusException.class) + .isThrownBy(replicate::getLastRelevantStatus) + .extracting(NoReplicateStatusException::getChainTaskId) + .isEqualTo(CHAIN_TASK_ID); + } + + @Test + void shouldNotGetLastRelevantStatusWhenOnlyWorkerLost() { + final Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + ReflectionTestUtils.setField(replicate, "statusUpdateList", List.of(ReplicateStatusUpdate.poolManagerRequest(WORKER_LOST))); + + assertThatExceptionOfType(NoReplicateStatusException.class) + .isThrownBy(replicate::getLastRelevantStatus) + .extracting(NoReplicateStatusException::getChainTaskId) + .isEqualTo(CHAIN_TASK_ID); + } + + @Test + void shouldNotGetLastRelevantStatusWhenOnlyRecovering() { + final Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + ReflectionTestUtils.setField(replicate, "statusUpdateList", List.of(ReplicateStatusUpdate.poolManagerRequest(RECOVERING))); + + assertThatExceptionOfType(NoReplicateStatusException.class) + .isThrownBy(replicate::getLastRelevantStatus) + .extracting(NoReplicateStatusException::getChainTaskId) + .isEqualTo(CHAIN_TASK_ID); + } + + @Test + void shouldNotGetLastRelevantStatusWhenWorkerLostAndyRecovering() { + final Replicate replicate = new Replicate(WALLET_WORKER, CHAIN_TASK_ID); + ReflectionTestUtils.setField(replicate, "statusUpdateList", List.of( + ReplicateStatusUpdate.poolManagerRequest(WORKER_LOST), + ReplicateStatusUpdate.poolManagerRequest(RECOVERING) + )); + + assertThatExceptionOfType(NoReplicateStatusException.class) + .isThrownBy(replicate::getLastRelevantStatus) + .extracting(NoReplicateStatusException::getChainTaskId) + .isEqualTo(CHAIN_TASK_ID); + } + // endregion } diff --git a/src/test/java/com/iexec/core/security/ChallengeServiceTests.java b/src/test/java/com/iexec/core/security/ChallengeServiceTests.java index 1ebc34448..e8c4ac306 100644 --- a/src/test/java/com/iexec/core/security/ChallengeServiceTests.java +++ b/src/test/java/com/iexec/core/security/ChallengeServiceTests.java @@ -27,6 +27,13 @@ class ChallengeServiceTests { private final ChallengeService challengeService = new ChallengeService(); + @Test + void shouldCreateNewChallengeAfterRemoval() { + String challenge1 = challengeService.getChallenge(WALLET_WORKER_1); + challengeService.removeChallenge(WALLET_WORKER_1, challenge1); + String challenge2 = challengeService.getChallenge(WALLET_WORKER_1); + assertThat(challenge1).isNotEqualTo(challenge2); + } @Test void shouldGetSameChallengeForSameWallet() { String challenge1 = challengeService.getChallenge(WALLET_WORKER_1); diff --git a/src/test/java/com/iexec/core/security/EIP712ChallengeServiceTests.java b/src/test/java/com/iexec/core/security/EIP712ChallengeServiceTests.java index 2cc652c43..fbf1e4895 100644 --- a/src/test/java/com/iexec/core/security/EIP712ChallengeServiceTests.java +++ b/src/test/java/com/iexec/core/security/EIP712ChallengeServiceTests.java @@ -23,7 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; -public class EIP712ChallengeServiceTests { +class EIP712ChallengeServiceTests { private static final String WALLET_ADDRESS_1 = "1a69b2eb604db8eba185df03ea4f5288dcbbd248"; private static final String WALLET_ADDRESS_2 = "2a69b2eb604db8eba185df03ea4f5288dcbbd248"; diff --git a/src/test/java/com/iexec/core/security/JwtTokenProviderTests.java b/src/test/java/com/iexec/core/security/JwtTokenProviderTests.java index 68fca9dcd..1282b8a79 100644 --- a/src/test/java/com/iexec/core/security/JwtTokenProviderTests.java +++ b/src/test/java/com/iexec/core/security/JwtTokenProviderTests.java @@ -16,32 +16,53 @@ package com.iexec.core.security; +import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.boot.info.BuildProperties; +import org.springframework.test.util.ReflectionTestUtils; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; + +import static com.iexec.core.security.JwtTokenProvider.KEY_SIZE; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; class JwtTokenProviderTests { + private static final String JWT_AUDIENCE = "iExec Scheduler vX.Y.Z"; private static final String WALLET_ADDRESS = "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"; - - @Mock - private ChallengeService challengeService; - - @InjectMocks + private static final SecureRandom secureRandom = new SecureRandom(); private JwtTokenProvider jwtTokenProvider; + private final byte[] secretKey = new byte[KEY_SIZE]; @BeforeEach void init() { MockitoAnnotations.openMocks(this); + secureRandom.nextBytes(secretKey); + BuildProperties buildProperties = mock(BuildProperties.class); + when(buildProperties.getVersion()).thenReturn("X.Y.Z"); + jwtTokenProvider = spy(new JwtTokenProvider(buildProperties)); + ReflectionTestUtils.setField(jwtTokenProvider, "secretKey", secretKey); } + //region createToken + @Test + void shouldReturnExistingTokenIfValid() { + String token1 = jwtTokenProvider.getOrCreateToken(WALLET_ADDRESS); + String token2 = jwtTokenProvider.getOrCreateToken(WALLET_ADDRESS); + assertThat(token1).isEqualTo(token2); + } + //endregion + //region resolveToken @Test void shouldResolveToken() { @@ -52,7 +73,7 @@ void shouldResolveToken() { @Test void shouldNotResolveTokenSinceNotValidOne() { - String notBearerToken = "Not Bearer eb604db8eba185df03ea4f5"; + String notBearerToken = "Not " + jwtTokenProvider.getOrCreateToken(WALLET_ADDRESS); String resolvedToken = jwtTokenProvider.resolveToken(notBearerToken); assertThat(resolvedToken).isNull(); } @@ -67,24 +88,75 @@ void shouldNotResolveTokenSinceNullOne() { //region isValidToken @Test void isValidTokenTrue() { - when(challengeService.getChallenge(WALLET_ADDRESS)).thenReturn("challenge"); - String token = jwtTokenProvider.createToken(WALLET_ADDRESS); + String token = jwtTokenProvider.getOrCreateToken(WALLET_ADDRESS); boolean isValidToken = jwtTokenProvider.isValidToken(token); assertThat(isValidToken).isTrue(); } @Test - void isValidTokenFalseSinceNotSameChallenge() { - when(challengeService.getChallenge(WALLET_ADDRESS)).thenReturn("challenge1", "challenge2"); - String token = jwtTokenProvider.createToken(WALLET_ADDRESS); + void isValidTokenFalseSinceBadAudience() { + Date now = new Date(); + String token = Jwts.builder() + .setAudience("iExec Scheduler") + .setSubject(WALLET_ADDRESS) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 10000L)) + .signWith(Keys.hmacShaKeyFor(secretKey), SignatureAlgorithm.HS256) + .compact(); + ConcurrentHashMap tokensMap = new ConcurrentHashMap<>(); + tokensMap.put(WALLET_ADDRESS, token); + ReflectionTestUtils.setField(jwtTokenProvider, "jwTokensMap", tokensMap); + boolean isValidToken = jwtTokenProvider.isValidToken(token); + assertThat(isValidToken).isFalse(); + } + + @Test + void isValidTokenFalseSinceExpired() { + Date now = new Date(); + String token = Jwts.builder() + .setAudience(JWT_AUDIENCE) + .setSubject(WALLET_ADDRESS) + .setIssuedAt(now) + .setExpiration(now) + .signWith(Keys.hmacShaKeyFor(secretKey), SignatureAlgorithm.HS256) + .compact(); + ConcurrentHashMap tokensMap = new ConcurrentHashMap<>(); + tokensMap.put(WALLET_ADDRESS, token); + ReflectionTestUtils.setField(jwtTokenProvider, "jwTokensMap", tokensMap); boolean isValidToken = jwtTokenProvider.isValidToken(token); assertThat(isValidToken).isFalse(); } + @Test + void isValidTokenFalseSinceNotSigned() { + String token = Jwts.builder() + .setAudience(JWT_AUDIENCE) + .setIssuedAt(new Date()) + .setSubject(WALLET_ADDRESS) + .compact(); + boolean isTokenValid = jwtTokenProvider.isValidToken(token); + assertThat(isTokenValid).isFalse(); + } + + @Test + void isValidTokenFalseSinceWronglySigned() { + final byte[] newKey = new byte[KEY_SIZE]; + secureRandom.nextBytes(newKey); + Date now = new Date(); + String token = Jwts.builder() + .setAudience(JWT_AUDIENCE) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + 10000L)) + .setSubject(WALLET_ADDRESS) + .signWith(Keys.hmacShaKeyFor(newKey), SignatureAlgorithm.HS256) + .compact(); + boolean isTokenValid = jwtTokenProvider.isValidToken(token); + assertThat(isTokenValid).isFalse(); + } + @Test void isValidTokenFalseSinceNotValidOne() { - when(challengeService.getChallenge(WALLET_ADDRESS)).thenReturn("challenge"); - jwtTokenProvider.createToken(WALLET_ADDRESS); + jwtTokenProvider.getOrCreateToken(WALLET_ADDRESS); boolean isValidToken = jwtTokenProvider.isValidToken("non.valid.token"); assertThat(isValidToken).isFalse(); } @@ -93,8 +165,7 @@ void isValidTokenFalseSinceNotValidOne() { //region getWalletAddress @Test void shouldGetCorrectWalletAddress() { - when(challengeService.getChallenge(WALLET_ADDRESS)).thenReturn("challenge"); - String token = jwtTokenProvider.createToken(WALLET_ADDRESS); + String token = jwtTokenProvider.getOrCreateToken(WALLET_ADDRESS); String walletAddress = jwtTokenProvider.getWalletAddress(token); assertThat(walletAddress).isEqualTo(WALLET_ADDRESS); } @@ -108,8 +179,7 @@ void shouldThrowJwtExceptionSinceNotValidToken() { //region getWalletAddressFromBearerToken @Test void shouldGetCorrectWalletAddressFromBearerToken() { - when(challengeService.getChallenge(WALLET_ADDRESS)).thenReturn("challenge"); - String token = jwtTokenProvider.createToken(WALLET_ADDRESS); + String token = jwtTokenProvider.getOrCreateToken(WALLET_ADDRESS); String bearerToken = "Bearer " + token; String walletAddress = jwtTokenProvider.getWalletAddressFromBearerToken(bearerToken); assertThat(walletAddress).isEqualTo(WALLET_ADDRESS); @@ -117,8 +187,7 @@ void shouldGetCorrectWalletAddressFromBearerToken() { @Test void shouldNotGetWalletAddressSinceNotValidBearerToken() { - when(challengeService.getChallenge(WALLET_ADDRESS)).thenReturn("challenge"); - String token = jwtTokenProvider.createToken(WALLET_ADDRESS); + String token = jwtTokenProvider.getOrCreateToken(WALLET_ADDRESS); String notBearerToken = "Not Bearer " + token; String walletAddress = jwtTokenProvider.getWalletAddressFromBearerToken(notBearerToken); assertThat(walletAddress).isEmpty(); diff --git a/src/test/java/com/iexec/core/sms/SmsServiceTests.java b/src/test/java/com/iexec/core/sms/SmsServiceTests.java index d1b9a8c63..cb3c0e9a9 100644 --- a/src/test/java/com/iexec/core/sms/SmsServiceTests.java +++ b/src/test/java/com/iexec/core/sms/SmsServiceTests.java @@ -1,39 +1,114 @@ package com.iexec.core.sms; +import com.iexec.common.tee.TeeFramework; +import com.iexec.common.tee.TeeUtils; import com.iexec.common.utils.BytesUtils; +import com.iexec.core.registry.PlatformRegistryConfiguration; import com.iexec.sms.api.SmsClient; +import com.iexec.sms.api.SmsClientProvider; import feign.FeignException; import feign.Request; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Collections; +import java.util.List; import java.util.Optional; +import java.util.stream.Stream; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; class SmsServiceTests { + private static final String GRAMINE_SMS_URL = "http://gramine-sms"; + private static final String SCONE_SMS_URL = "http://scone-sms"; private static final String CHAIN_TASK_ID = "chainTaskId"; + private static final String url = "url"; @Mock private SmsClient smsClient; + @Mock + private SmsClientProvider smsClientProvider; + @Mock + private PlatformRegistryConfiguration registryConfiguration; + @InjectMocks private SmsService smsService; @BeforeEach void init() { MockitoAnnotations.openMocks(this); + when(registryConfiguration.getSconeSms()).thenReturn(SCONE_SMS_URL); + when(registryConfiguration.getGramineSms()).thenReturn(GRAMINE_SMS_URL); + } + + // region isSmsClientReady + static Stream validData() { + List supportedTeeTags = + List.of( + TeeUtils.TEE_SCONE_ONLY_TAG, + TeeUtils.TEE_GRAMINE_ONLY_TAG); + //Ensure all TeeEnclaveProvider are handled + // (adding a new one would break assertion) + Assertions.assertThat(supportedTeeTags) + .hasSize(TeeFramework.values().length); + return Stream.of( + Arguments.of(supportedTeeTags.get(0), SCONE_SMS_URL), + Arguments.of(supportedTeeTags.get(1), GRAMINE_SMS_URL) + ); + } + + @ParameterizedTest + @MethodSource("validData") + void shouldGetVerifiedSmsUrl(String inputTag, String expectedSmsUrl) { + when(smsClientProvider.getSmsClient(expectedSmsUrl)).thenReturn(smsClient); + when(smsClient.getTeeFramework()).thenReturn(TeeUtils.getTeeFramework(inputTag)); + + Assertions.assertThat(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, inputTag)) + .isEqualTo(Optional.of(expectedSmsUrl)); + + verify(smsClientProvider).getSmsClient(expectedSmsUrl); + verify(smsClient).getTeeFramework(); + } + + @Test + void shouldNotGetVerifiedSmsUrlSinceCannotGetEnclaveProviderFromTag() { + Assertions.assertThat(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, "0xabc")) + .isEmpty(); + + verify(smsClientProvider, times(0)).getSmsClient(anyString()); + verify(smsClient, times(0)).getTeeFramework(); } + @Test + void shouldNotGetVerifiedSmsUrlSinceSinceWrongTeeEnclaveProviderOnRemoteSms() { + when(smsClientProvider.getSmsClient(GRAMINE_SMS_URL)).thenReturn(smsClient); + when(smsClient.getTeeFramework()).thenReturn(TeeFramework.SCONE); + + Assertions.assertThat(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, TeeUtils.TEE_GRAMINE_ONLY_TAG)) + .isEmpty(); + + verify(smsClientProvider).getSmsClient(GRAMINE_SMS_URL); + verify(smsClient).getTeeFramework(); + } + // endregion + + // region getEnclaveChallenge @Test void shouldGetEmptyAddressForStandardTask() { - Assertions.assertThat(smsService.getEnclaveChallenge(CHAIN_TASK_ID, false)) + when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); + when(smsClient.generateTeeChallenge(CHAIN_TASK_ID)).thenReturn(""); + + Assertions.assertThat(smsService.getEnclaveChallenge(CHAIN_TASK_ID, "")) .get() .isEqualTo(BytesUtils.EMPTY_ADDRESS); verify(smsClient, never()).generateTeeChallenge(anyString()); @@ -42,9 +117,10 @@ void shouldGetEmptyAddressForStandardTask() { @Test void shouldGetEnclaveChallengeForTeeTask() { String expected = "challenge"; + when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); when(smsClient.generateTeeChallenge(CHAIN_TASK_ID)).thenReturn(expected); - Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, true); + Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, url); verify(smsClient).generateTeeChallenge(CHAIN_TASK_ID); Assertions.assertThat(received) .get() @@ -53,30 +129,26 @@ void shouldGetEnclaveChallengeForTeeTask() { @Test void shouldNotGetEnclaveChallengeForTeeTaskWhenEmptySmsResponse() { + when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); when(smsClient.generateTeeChallenge(CHAIN_TASK_ID)).thenReturn(""); - Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, true); - verify(smsClient).generateTeeChallenge(CHAIN_TASK_ID); - Assertions.assertThat(received).isEmpty(); - } - - @Test - void shouldNotGetEnclaveChallengeForTeeTaskWhenNullSmsResponse() { - when(smsClient.generateTeeChallenge(CHAIN_TASK_ID)).thenReturn(null); - - Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, true); + Optional received = smsService.getEnclaveChallenge(CHAIN_TASK_ID, url); verify(smsClient).generateTeeChallenge(CHAIN_TASK_ID); Assertions.assertThat(received).isEmpty(); } + // endregion + // region generateEnclaveChallenge @Test void shouldNotGetEnclaveChallengeOnFeignException() { - Request request = Request.create(Request.HttpMethod.HEAD, "http://localhost", + when(smsClientProvider.getSmsClient(url)).thenReturn(smsClient); + Request request = Request.create(Request.HttpMethod.HEAD, url, Collections.emptyMap(), Request.Body.empty(), null); Assertions.assertThat(smsService.generateEnclaveChallenge( new FeignException.Unauthorized("", request, new byte[0], null), - CHAIN_TASK_ID - ) + CHAIN_TASK_ID, + url) ).isEmpty(); verifyNoInteractions(smsClient); } + // endregion } \ No newline at end of file diff --git a/src/test/java/com/iexec/core/task/TaskControllerTests.java b/src/test/java/com/iexec/core/task/TaskControllerTests.java index 426c812d7..5702f0755 100644 --- a/src/test/java/com/iexec/core/task/TaskControllerTests.java +++ b/src/test/java/com/iexec/core/task/TaskControllerTests.java @@ -21,7 +21,6 @@ import com.iexec.common.chain.eip712.entity.EIP712Challenge; import com.iexec.common.replicate.ComputeLogs; import com.iexec.common.task.TaskDescription; -import com.iexec.common.utils.CredentialsUtils; import com.iexec.core.chain.IexecHubService; import com.iexec.core.logs.TaskLogs; import com.iexec.core.logs.TaskLogsService; @@ -29,8 +28,7 @@ import com.iexec.core.replicate.ReplicateModel; import com.iexec.core.replicate.ReplicatesService; import com.iexec.core.security.EIP712ChallengeService; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; +import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -38,21 +36,16 @@ import org.mockito.MockitoAnnotations; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.web3j.crypto.Credentials; import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Keys; -import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -@Slf4j class TaskControllerTests { private static final String TASK_ID = "0xtask"; @@ -72,21 +65,22 @@ class TaskControllerTests { @InjectMocks private TaskController taskController; @BeforeEach - void setUp() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + @SneakyThrows + void setUp() { MockitoAnnotations.openMocks(this); EIP712Domain domain = new EIP712Domain(); challenge = new EIP712Challenge(domain, Challenge.builder().challenge("challenge").build()); badChallenge = new EIP712Challenge(domain, Challenge.builder().challenge("bad-challenge").build()); ecKeyPair = Keys.createEcKeyPair(); - requesterAddress = CredentialsUtils.getAddress(String.format("0x%032x", ecKeyPair.getPrivateKey())); + requesterAddress = Credentials.create(ecKeyPair).getAddress(); signature = challenge.signMessage(ecKeyPair); } //region utilities - String generateWalletAddress() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + @SneakyThrows + String generateWalletAddress() { ECKeyPair ecKeyPair = Keys.createEcKeyPair(); - BigInteger pk = ecKeyPair.getPrivateKey(); - return CredentialsUtils.getAddress(String.format("0x%032x", pk)); + return Credentials.create(ecKeyPair).getAddress(); } //endregion @@ -208,7 +202,7 @@ void shouldGetTaskLogsWhenAuthenticated() { when(challengeService.getChallenge(requesterAddress)).thenReturn(challenge); when(taskLogsService.getTaskLogs(TASK_ID)).thenReturn(Optional.of(taskStdout)); ResponseEntity response = taskController.getTaskLogs(TASK_ID, authorization); - assertTrue(response.getStatusCode().is2xxSuccessful()); + assertEquals(HttpStatus.OK, response.getStatusCode()); verify(iexecHubService).getTaskDescription(TASK_ID); } @@ -248,11 +242,10 @@ void shouldFailToGetTaskLogsWhenInvalidAuthorization() { } @Test - void shouldFailToGetTaskLogsWhenNotTaskRequester() - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + void shouldFailToGetTaskLogsWhenNotTaskRequester() { String notRequesterAddress = generateWalletAddress(); TaskDescription description = TaskDescription.builder() - .requester(CredentialsUtils.getAddress(notRequesterAddress)) + .requester(notRequesterAddress) .build(); String authorization = String.join("_", challenge.getHash(), signature, requesterAddress); when(iexecHubService.getTaskDescription(TASK_ID)).thenReturn(description); @@ -275,7 +268,7 @@ void shouldGetComputeLogsWhenAuthenticated() { when(challengeService.getChallenge(requesterAddress)).thenReturn(challenge); when(taskLogsService.getComputeLogs(TASK_ID, WORKER_ADDRESS)).thenReturn(Optional.of(computeLogs)); ResponseEntity response = taskController.getComputeLogs(TASK_ID, WORKER_ADDRESS, authorization); - assertTrue(response.getStatusCode().is2xxSuccessful()); + assertEquals(HttpStatus.OK, response.getStatusCode()); verify(iexecHubService).getTaskDescription(TASK_ID); } @@ -315,11 +308,10 @@ void shouldFailToGetComputeLogsWhenInvalidAuthorization() { } @Test - void shouldFailToGetComputeLogsWhenNotTaskRequester() - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + void shouldFailToGetComputeLogsWhenNotTaskRequester() { String notRequesterAddress = generateWalletAddress(); TaskDescription description = TaskDescription.builder() - .requester(CredentialsUtils.getAddress(notRequesterAddress)) + .requester(notRequesterAddress) .build(); String authorization = String.join("_", challenge.getHash(), signature, requesterAddress); when(iexecHubService.getTaskDescription(TASK_ID)).thenReturn(description); diff --git a/src/test/java/com/iexec/core/task/TaskModelTests.java b/src/test/java/com/iexec/core/task/TaskModelTests.java index c4634da4d..74d63704a 100644 --- a/src/test/java/com/iexec/core/task/TaskModelTests.java +++ b/src/test/java/com/iexec/core/task/TaskModelTests.java @@ -17,7 +17,6 @@ package com.iexec.core.task; import com.iexec.common.dapp.DappType; -import com.iexec.common.tee.TeeUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -29,7 +28,7 @@ class TaskModelTests { public static final String CHAIN_TASK_ID = "task"; public static final long MAX_EXECUTION_TIME = 1; - public static final String TAG = TeeUtils.TEE_TAG; + public static final String TAG = "0xa"; public static final DappType DAPP_TYPE = DappType.DOCKER; public static final String DAPP_NAME = "name"; public static final String COMMAND_LINE = "line"; diff --git a/src/test/java/com/iexec/core/task/TaskServiceRealRepositoryTest.java b/src/test/java/com/iexec/core/task/TaskServiceRealRepositoryTest.java new file mode 100644 index 000000000..ed8b08a04 --- /dev/null +++ b/src/test/java/com/iexec/core/task/TaskServiceRealRepositoryTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2023 IEXEC BLOCKCHAIN TECH + * + * 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 + * + * http://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. + */ + +package com.iexec.core.task; + +import com.iexec.common.chain.ChainUtils; +import com.iexec.core.chain.IexecHubService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +import static com.iexec.core.task.TaskTestsUtils.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +@DataMongoTest +@Testcontainers +class TaskServiceRealRepositoryTest { + private final long maxExecutionTime = 60000; + private final Date contributionDeadline = new Date(); + private final Date finalDeadline = new Date(); + + @Container + private static final MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.2")); + + @DynamicPropertySource + static void registerProperties(DynamicPropertyRegistry registry) { + registry.add("spring.data.mongodb.host", mongoDBContainer::getContainerIpAddress); + registry.add("spring.data.mongodb.port", () -> mongoDBContainer.getMappedPort(27017)); + } + + @Autowired + private TaskRepository taskRepository; + + @Mock + private IexecHubService iexecHubService; + + private TaskService taskService; + + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + taskService = new TaskService(taskRepository, iexecHubService); + } + + @Test + void shouldAddTaskASingleTime() { + final int concurrentRequests = 5; + final String expectedChainTaskId = ChainUtils.generateChainTaskId(CHAIN_DEAL_ID, 0); + + // Let's start n `taskService.addTask` at the same time. + // Without any sync mechanism, this should fail + // as it'll try to add more than once the same task - with the same key - to the DB. + final ExecutorService executorService = Executors.newFixedThreadPool(concurrentRequests); + final List>> executions = new ArrayList<>(concurrentRequests); + for (int i = 0; i < concurrentRequests; i++) { + executions.add(executorService.submit(() -> taskService.addTask(CHAIN_DEAL_ID, 0, 0, DAPP_NAME, COMMAND_LINE, + 2, maxExecutionTime, "0x0", contributionDeadline, finalDeadline))); + } + + // Let's wait for the `taskService.addTask` to complete and retrieve the results. + List> results = executions.stream().map(execution -> { + try { + return execution.get(1, TimeUnit.MINUTES); + } catch (ExecutionException e) { + if (e.getCause() instanceof DuplicateKeyException) { + fail("Task has been added twice. Should not happen!"); + } + throw new RuntimeException("Something went wrong.", e); + } catch (InterruptedException | TimeoutException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + + // Check one execution has added the task, + // while the others have failed. + assertThat(results).hasSize(concurrentRequests); + final List nonEmptyResults = results + .stream() + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + assertThat(nonEmptyResults).hasSize(1); + assertThat(nonEmptyResults.get(0).getChainTaskId()).isEqualTo(expectedChainTaskId); + + // Finally, let's simply check the task has effectively been added. + assertThat(taskRepository.findByChainTaskId(CHAIN_TASK_ID)).isPresent(); + } +} diff --git a/src/test/java/com/iexec/core/task/TaskServiceTests.java b/src/test/java/com/iexec/core/task/TaskServiceTests.java index 41dcef919..7d9d0ddf3 100644 --- a/src/test/java/com/iexec/core/task/TaskServiceTests.java +++ b/src/test/java/com/iexec/core/task/TaskServiceTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.*; +import org.springframework.dao.DuplicateKeyException; import org.springframework.data.domain.Sort; import java.time.Instant; @@ -66,7 +67,7 @@ void init() { void shouldNotGetTaskWithTrust() { when(taskRepository.findByChainTaskId("dummyId")).thenReturn(Optional.empty()); Optional task = taskService.getTaskByChainTaskId("dummyId"); - assertThat(task.isPresent()).isFalse(); + assertThat(task).isEmpty(); } @Test @@ -75,8 +76,7 @@ void shouldGetOneTask() { when(taskRepository.findByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(task)); Optional optional = taskService.getTaskByChainTaskId(CHAIN_TASK_ID); - assertThat(optional.isPresent()).isTrue(); - assertThat(optional).isEqualTo(Optional.of(task)); + assertThat(optional).contains(task); } @Test @@ -87,18 +87,18 @@ void shouldAddTask() { when(taskRepository.save(any())).thenReturn(task); Optional saved = taskService.addTask(CHAIN_DEAL_ID, 0, 0, DAPP_NAME, COMMAND_LINE, 2, maxExecutionTime, "0x0", contributionDeadline, finalDeadline); - assertThat(saved).isPresent(); - assertThat(saved).isEqualTo(Optional.of(task)); + assertThat(saved).contains(task); } @Test void shouldNotAddTask() { Task task = getStubTask(maxExecutionTime); task.changeStatus(TaskStatus.INITIALIZED); - when(taskRepository.findByChainDealIdAndTaskIndex(CHAIN_DEAL_ID, 0)).thenReturn(Optional.of(task)); + when(taskRepository.save(any())).thenThrow(DuplicateKeyException.class); + Optional saved = taskService.addTask(CHAIN_DEAL_ID, 0, 0, DAPP_NAME, COMMAND_LINE, 2, maxExecutionTime, "0x0", contributionDeadline, finalDeadline); - assertThat(saved).isEqualTo(Optional.empty()); + assertThat(saved).isEmpty(); } @Test @@ -161,7 +161,7 @@ void shouldNotFindByCurrentStatusList() { @Test void shouldGetInitializedOrRunningTasks() { Task task = mock(Task.class); - when(taskRepository.findFirstByCurrentStatusInAndTagNotAndChainTaskIdNotIn( + when(taskRepository.findFirstByCurrentStatusInAndTagNotInAndChainTaskIdNotIn( eq(Arrays.asList(INITIALIZED, RUNNING)), any(), eq(Collections.emptyList()), diff --git a/src/test/java/com/iexec/core/task/TaskTests.java b/src/test/java/com/iexec/core/task/TaskTests.java index 361aaac11..7c9fb97f2 100644 --- a/src/test/java/com/iexec/core/task/TaskTests.java +++ b/src/test/java/com/iexec/core/task/TaskTests.java @@ -37,24 +37,24 @@ class TaskTests { void shouldInitializeProperly(){ Task task = new Task(DAPP_NAME, COMMAND_LINE, 2); - assertThat(task.getDateStatusList().size()).isEqualTo(1); + assertThat(task.getDateStatusList()).hasSize(1); assertThat(task.getDateStatusList().get(0).getStatus()).isEqualTo(TaskStatus.RECEIVED); } @Test void shouldSetCurrentStatus() { Task task = new Task(DAPP_NAME, COMMAND_LINE, 2); - assertThat(task.getDateStatusList().size()).isEqualTo(1); + assertThat(task.getDateStatusList()).hasSize(1); assertThat(task.getCurrentStatus()).isEqualTo(TaskStatus.RECEIVED); task.changeStatus(TaskStatus.INITIALIZED); - assertThat(task.getDateStatusList().size()).isEqualTo(2); + assertThat(task.getDateStatusList()).hasSize(2); assertThat(task.getDateStatusList().get(0).getStatus()).isEqualTo(TaskStatus.RECEIVED); assertThat(task.getDateStatusList().get(1).getStatus()).isEqualTo(TaskStatus.INITIALIZED); assertThat(task.getCurrentStatus()).isEqualTo(TaskStatus.INITIALIZED); task.changeStatus(TaskStatus.RUNNING); - assertThat(task.getDateStatusList().size()).isEqualTo(3); + assertThat(task.getDateStatusList()).hasSize(3); assertThat(task.getDateStatusList().get(2).getStatus()).isEqualTo(TaskStatus.RUNNING); assertThat(task.getCurrentStatus()).isEqualTo(TaskStatus.RUNNING); } diff --git a/src/test/java/com/iexec/core/task/TaskTestsUtils.java b/src/test/java/com/iexec/core/task/TaskTestsUtils.java index c2458591c..769a71a31 100644 --- a/src/test/java/com/iexec/core/task/TaskTestsUtils.java +++ b/src/test/java/com/iexec/core/task/TaskTestsUtils.java @@ -16,6 +16,7 @@ package com.iexec.core.task; +import com.iexec.common.tee.TeeUtils; import com.iexec.common.utils.BytesUtils; import java.time.Instant; @@ -32,7 +33,7 @@ public class TaskTestsUtils { public final static String DAPP_NAME = "dappName"; public final static String COMMAND_LINE = "commandLine"; public final static String NO_TEE_TAG = BytesUtils.EMPTY_HEX_STRING_32; - public final static String TEE_TAG = "0x0000000000000000000000000000000000000000000000000000000000000001"; + public final static String TEE_TAG = TeeUtils.TEE_SCONE_ONLY_TAG; //any supported TEE tag public final static String RESULT_LINK = "/ipfs/the_result_string"; public static Task getStubTask(long maxExecutionTime) { diff --git a/src/test/java/com/iexec/core/task/listener/TaskListenerTest.java b/src/test/java/com/iexec/core/task/listener/TaskListenerTest.java index 0dc3c4060..16a096af9 100644 --- a/src/test/java/com/iexec/core/task/listener/TaskListenerTest.java +++ b/src/test/java/com/iexec/core/task/listener/TaskListenerTest.java @@ -16,6 +16,7 @@ package com.iexec.core.task.listener; +import com.iexec.common.lifecycle.purge.PurgeService; import com.iexec.common.notification.TaskNotification; import com.iexec.common.notification.TaskNotificationType; import com.iexec.common.task.TaskAbortCause; @@ -56,6 +57,8 @@ class TaskListenerTest { private ReplicatesService replicatesService; @Mock private WorkerService workerService; + @Mock + private PurgeService purgeService; @InjectMocks private TaskListeners taskListeners; @@ -157,6 +160,7 @@ void onTaskCompletedEvent() { taskListeners.onTaskCompletedEvent(event); verify(notificationService).sendTaskNotification(any()); verify(workerService).removeChainTaskIdFromWorker(CHAIN_TASK_ID, WALLET1); + verify(purgeService).purgeAllServices(CHAIN_TASK_ID); // TODO capture args } @@ -174,6 +178,7 @@ void onTaskFailedEvent() { .build() ); verify(workerService).removeChainTaskIdFromWorker(CHAIN_TASK_ID, WALLET1); + verify(purgeService).purgeAllServices(CHAIN_TASK_ID); } @Test @@ -190,5 +195,6 @@ void onTaskRunningFailedEvent() { .build() ); verify(workerService).removeChainTaskIdFromWorker(CHAIN_TASK_ID, WALLET1); + verify(purgeService).purgeAllServices(CHAIN_TASK_ID); } } diff --git a/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTest.java b/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTest.java index 4853433eb..5bd4ca9f7 100644 --- a/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTest.java +++ b/src/test/java/com/iexec/core/task/update/TaskUpdateManagerTest.java @@ -22,6 +22,7 @@ import com.iexec.common.replicate.ReplicateStatus; import com.iexec.common.replicate.ReplicateStatusModifier; import com.iexec.common.replicate.ReplicateStatusUpdate; +import com.iexec.common.tee.TeeUtils; import com.iexec.common.utils.BytesUtils; import com.iexec.common.utils.DateTimeUtils; import com.iexec.core.chain.IexecHubService; @@ -32,16 +33,19 @@ import com.iexec.core.replicate.Replicate; import com.iexec.core.replicate.ReplicatesList; import com.iexec.core.replicate.ReplicatesService; +import com.iexec.core.sms.SmsService; import com.iexec.core.task.Task; import com.iexec.core.task.TaskService; import com.iexec.core.task.TaskStatus; -import com.iexec.core.sms.SmsService; import com.iexec.core.task.event.PleaseUploadEvent; import com.iexec.core.worker.Worker; import com.iexec.core.worker.WorkerService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.*; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.springframework.context.ApplicationEventPublisher; import java.time.Instant; @@ -55,10 +59,12 @@ import static com.iexec.core.task.TaskStatus.*; import static com.iexec.core.task.TaskTestsUtils.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; class TaskUpdateManagerTest { private final long maxExecutionTime = 60000; + private static final String smsUrl = "smsUrl"; @Mock private WorkerService workerService; @@ -307,6 +313,26 @@ void shouldNotUpdateReceived2InitializingSinceAfterContributionDeadline() { assertThat(task.getCurrentStatus()).isEqualTo(RECEIVED); } + @Test + void shouldNotUpdateReceived2InitializingSinceNoSmsClient() { + Task task = getStubTask(maxExecutionTime); + task.changeStatus(RECEIVED); + task.setChainTaskId(CHAIN_TASK_ID); + + when(taskService.getTaskByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(task)); + when(iexecHubService.hasEnoughGas()).thenReturn(true); + when(iexecHubService.isTaskInUnsetStatusOnChain(CHAIN_DEAL_ID, 0)).thenReturn(true); + when(iexecHubService.isBeforeContributionDeadline(task.getChainDealId())) + .thenReturn(true); + when(taskService.updateTask(task)).thenReturn(Optional.of(task)); + when(blockchainAdapterService.requestInitialize(CHAIN_DEAL_ID, 0)).thenReturn(Optional.of(CHAIN_TASK_ID)); + when(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, task.getTag())) + .thenReturn(Optional.of(smsUrl)); + + taskUpdateManager.updateTask(CHAIN_TASK_ID); + assertThat(task.getCurrentStatus()).isEqualTo(FAILED); + } + @Test void shouldUpdateInitializing2InitailizeFailedSinceChainTaskIdIsEmpty() { Task task = getStubTask(maxExecutionTime); @@ -343,7 +369,7 @@ void shouldUpdateInitializing2InitializeFailedSinceEnclaveChallengeIsEmpty() { when(iexecHubService.getChainTask(CHAIN_TASK_ID)).thenReturn(Optional.of(ChainTask.builder() .contributionDeadline(DateTimeUtils.addMinutesToDate(new Date(), 60).getTime()) .build())); - when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, false)).thenReturn(Optional.empty()); + when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, smsUrl)).thenReturn(Optional.empty()); taskUpdateManager.updateTask(task.getChainTaskId()); @@ -352,10 +378,12 @@ void shouldUpdateInitializing2InitializeFailedSinceEnclaveChallengeIsEmpty() { } @Test - void shouldUpdateReceived2Initializing2Initialized() { + void shouldUpdateReceived2Initializing2InitializedOnStandard() { Task task = getStubTask(maxExecutionTime); + String tag = NO_TEE_TAG; task.changeStatus(RECEIVED); task.setChainTaskId(CHAIN_TASK_ID); + task.setTag(tag); when(taskService.getTaskByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(task)); when(iexecHubService.hasEnoughGas()).thenReturn(true); @@ -369,7 +397,7 @@ void shouldUpdateReceived2Initializing2Initialized() { when(iexecHubService.getChainTask(CHAIN_TASK_ID)).thenReturn(Optional.of(ChainTask.builder() .contributionDeadline(DateTimeUtils.addMinutesToDate(new Date(), 60).getTime()) .build())); - when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, false)).thenReturn(Optional.of(BytesUtils.EMPTY_ADDRESS)); + when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, null)).thenReturn(Optional.of(BytesUtils.EMPTY_ADDRESS)); taskUpdateManager.updateTask(CHAIN_TASK_ID); assertThat(task.getChainDealId()).isEqualTo(CHAIN_DEAL_ID); @@ -378,6 +406,82 @@ void shouldUpdateReceived2Initializing2Initialized() { assertThat(task.getDateStatusList().get(task.getDateStatusList().size() - 3).getStatus()).isEqualTo(RECEIVED); assertThat(task.getCurrentStatus()).isEqualTo(INITIALIZED); assertThat(task.getEnclaveChallenge()).isEqualTo(BytesUtils.EMPTY_ADDRESS); + assertThat(task.getSmsUrl()).isNull(); + verify(smsService, times(0)).getVerifiedSmsUrl(anyString(), anyString()); + verify(taskService, times(2)).updateTask(task); //initializing & initialized + } + + + @Test + void shouldUpdateReceived2Initializing2InitializedOnTee() { + Task task = getStubTask(maxExecutionTime); + String tag = TeeUtils.TEE_GRAMINE_ONLY_TAG; + task.changeStatus(RECEIVED); + task.setChainTaskId(CHAIN_TASK_ID); + task.setTag(tag);// Any TEE would be fine + + when(taskService.getTaskByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(task)); + when(iexecHubService.hasEnoughGas()).thenReturn(true); + when(iexecHubService.isTaskInUnsetStatusOnChain(CHAIN_DEAL_ID, 0)).thenReturn(true); + when(iexecHubService.isBeforeContributionDeadline(task.getChainDealId())) + .thenReturn(true); + + when(taskService.updateTask(task)).thenReturn(Optional.of(task)); + when(blockchainAdapterService.requestInitialize(CHAIN_DEAL_ID, 0)).thenReturn(Optional.of(CHAIN_TASK_ID)); + when(blockchainAdapterService.isInitialized(CHAIN_TASK_ID)).thenReturn(Optional.of(true)); + when(iexecHubService.getChainTask(CHAIN_TASK_ID)).thenReturn(Optional.of(ChainTask.builder() + .contributionDeadline(DateTimeUtils.addMinutesToDate(new Date(), 60).getTime()) + .build())); + when(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, tag)) + .thenReturn(Optional.of(smsUrl)); + when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, smsUrl)).thenReturn(Optional.of(BytesUtils.EMPTY_ADDRESS)); + + taskUpdateManager.updateTask(CHAIN_TASK_ID); + assertThat(task.getChainDealId()).isEqualTo(CHAIN_DEAL_ID); + assertThat(task.getDateStatusList().get(task.getDateStatusList().size() - 1).getStatus()).isEqualTo(INITIALIZED); + assertThat(task.getDateStatusList().get(task.getDateStatusList().size() - 2).getStatus()).isEqualTo(INITIALIZING); + assertThat(task.getDateStatusList().get(task.getDateStatusList().size() - 3).getStatus()).isEqualTo(RECEIVED); + assertThat(task.getCurrentStatus()).isEqualTo(INITIALIZED); + assertThat(task.getEnclaveChallenge()).isEqualTo(BytesUtils.EMPTY_ADDRESS); + assertThat(task.getSmsUrl()).isEqualTo(smsUrl); + verify(smsService, times(1)).getVerifiedSmsUrl(CHAIN_TASK_ID, tag); + verify(taskService, times(3)).updateTask(task); //save smsurl, INITIALIZING & INITIALIZED + } + + @Test + void shouldNotUpdateReceived2Initializing2InitializedOnTeeSinceCannotRetrieveSmsUrl() { + Task task = getStubTask(maxExecutionTime); + String tag = TeeUtils.TEE_GRAMINE_ONLY_TAG; + task.changeStatus(RECEIVED); + task.setChainTaskId(CHAIN_TASK_ID); + task.setTag(tag);// Any TEE would be fine + + when(taskService.getTaskByChainTaskId(CHAIN_TASK_ID)).thenReturn(Optional.of(task)); + when(iexecHubService.hasEnoughGas()).thenReturn(true); + when(iexecHubService.isTaskInUnsetStatusOnChain(CHAIN_DEAL_ID, 0)).thenReturn(true); + when(iexecHubService.isBeforeContributionDeadline(task.getChainDealId())) + .thenReturn(true); + + when(taskService.updateTask(task)).thenReturn(Optional.of(task)); + when(blockchainAdapterService.requestInitialize(CHAIN_DEAL_ID, 0)).thenReturn(Optional.of(CHAIN_TASK_ID)); + when(blockchainAdapterService.isInitialized(CHAIN_TASK_ID)).thenReturn(Optional.of(true)); + when(iexecHubService.getChainTask(CHAIN_TASK_ID)).thenReturn(Optional.of(ChainTask.builder() + .contributionDeadline(DateTimeUtils.addMinutesToDate(new Date(), 60).getTime()) + .build())); + when(smsService.getVerifiedSmsUrl(CHAIN_TASK_ID, tag)).thenReturn(Optional.empty()); + when(smsService.getEnclaveChallenge(CHAIN_TASK_ID, null)).thenReturn(Optional.of(BytesUtils.EMPTY_ADDRESS)); + + taskUpdateManager.updateTask(CHAIN_TASK_ID); + assertThat(task.getChainDealId()).isEqualTo(CHAIN_DEAL_ID); + assertThat(task.getDateStatusList().get(task.getDateStatusList().size() - 1).getStatus()).isEqualTo(FAILED); + assertThat(task.getDateStatusList().get(task.getDateStatusList().size() - 2).getStatus()).isEqualTo(INITIALIZE_FAILED); + assertThat(task.getDateStatusList().get(task.getDateStatusList().size() - 3).getStatus()).isEqualTo(RECEIVED); + assertThat(task.getCurrentStatus()).isEqualTo(FAILED); + assertThat(task.getEnclaveChallenge()).isNull(); + assertThat(task.getSmsUrl()).isNull(); + verify(smsService, times(1)).getVerifiedSmsUrl(CHAIN_TASK_ID, tag); + verify(smsService, times(0)).getEnclaveChallenge(anyString(), anyString()); + verify(taskService, times(2)).updateTask(task); // INITIALIZE_FAILED & FAILED } // Tests on initializing2Initialized transition @@ -670,9 +774,7 @@ void shouldUpdateRunning2RunningFailedOn1Worker() { // 1 replicate has tried to run the task: // - R1 is in `COMPUTE_FAILED` status; - Replicate replicate1 = new Replicate(); - replicate1.setWalletAddress(WALLET_WORKER_1); - replicate1.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate1 = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate1.setStatusUpdateList(new ArrayList<>()); replicate1.updateStatus(ReplicateStatus.COMPUTE_FAILED, ReplicateStatusModifier.WORKER); @@ -704,15 +806,11 @@ void shouldUpdateRunning2RunningFailedOn2Workers() { // 2 replicates have tried to run the task: // - R1 is in `COMPUTE_FAILED` status; // - R2 is in `APP_DOWNLOAD_FAILED` status. - Replicate replicate1 = new Replicate(); - replicate1.setWalletAddress(WALLET_WORKER_1); - replicate1.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate1 = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate1.setStatusUpdateList(new ArrayList<>()); replicate1.updateStatus(ReplicateStatus.COMPUTE_FAILED, ReplicateStatusModifier.WORKER); - Replicate replicate2 = new Replicate(); - replicate2.setWalletAddress(WALLET_WORKER_2); - replicate2.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate2 = new Replicate(WALLET_WORKER_2, CHAIN_TASK_ID); replicate2.setStatusUpdateList(new ArrayList<>()); replicate2.updateStatus(ReplicateStatus.APP_DOWNLOAD_FAILED, ReplicateStatusModifier.WORKER); @@ -743,9 +841,7 @@ void shouldNotUpdateRunning2RunningFailedOn1WorkerAsNonTeeTask() { // 1 replicate has tried to run the task: // - R1 is in `COMPUTE_FAILED` status; - Replicate replicate1 = new Replicate(); - replicate1.setWalletAddress(WALLET_WORKER_1); - replicate1.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate1 = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate1.setStatusUpdateList(new ArrayList<>()); replicate1.updateStatus(ReplicateStatus.COMPUTE_FAILED, ReplicateStatusModifier.WORKER); @@ -776,15 +872,11 @@ void shouldNotUpdateRunning2RunningFailedOn2WorkersAsNonTeeTask() { // 2 replicates have tried to run the task: // - R1 is in `COMPUTE_FAILED` status; // - R2 is in `APP_DOWNLOAD_FAILED` status. - Replicate replicate1 = new Replicate(); - replicate1.setWalletAddress(WALLET_WORKER_1); - replicate1.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate1 = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate1.setStatusUpdateList(new ArrayList<>()); replicate1.updateStatus(ReplicateStatus.COMPUTE_FAILED, ReplicateStatusModifier.WORKER); - Replicate replicate2 = new Replicate(); - replicate2.setWalletAddress(WALLET_WORKER_2); - replicate2.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate2 = new Replicate(WALLET_WORKER_2, CHAIN_TASK_ID); replicate2.setStatusUpdateList(new ArrayList<>()); replicate2.updateStatus(ReplicateStatus.APP_DOWNLOAD_FAILED, ReplicateStatusModifier.WORKER); @@ -815,15 +907,11 @@ void shouldNotUpdateRunning2AllWorkersFailedSinceOneStillComputing() { // 2 replicates have tried to run the task: // - R1 is in `COMPUTE_FAILED` status; // - R2 is in `COMPUTING` status. - Replicate replicate1 = new Replicate(); - replicate1.setWalletAddress(WALLET_WORKER_1); - replicate1.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate1 = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate1.setStatusUpdateList(new ArrayList<>()); replicate1.updateStatus(ReplicateStatus.COMPUTE_FAILED, ReplicateStatusModifier.WORKER); - Replicate replicate2 = new Replicate(); - replicate2.setWalletAddress(WALLET_WORKER_2); - replicate2.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate2 = new Replicate(WALLET_WORKER_2, CHAIN_TASK_ID); replicate2.setStatusUpdateList(new ArrayList<>()); replicate2.updateStatus(ReplicateStatus.COMPUTING, ReplicateStatusModifier.WORKER); @@ -855,15 +943,11 @@ void shouldNotUpdateRunning2AllWorkersFailedSinceOneHasReachedComputed() { // - R1 is in `COMPUTE_FAILED` status; // - R2 is in `CONTRIBUTE_FAILED` status. // Worker of R2 has been lost. - Replicate replicate1 = new Replicate(); - replicate1.setWalletAddress(WALLET_WORKER_1); - replicate1.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate1 = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate1.setStatusUpdateList(new ArrayList<>()); replicate1.updateStatus(ReplicateStatus.COMPUTE_FAILED, ReplicateStatusModifier.WORKER); - Replicate replicate2 = new Replicate(); - replicate2.setWalletAddress(WALLET_WORKER_2); - replicate2.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate2 = new Replicate(WALLET_WORKER_2, CHAIN_TASK_ID); replicate2.setStatusUpdateList(new ArrayList<>()); replicate2.updateStatus(ReplicateStatus.CONTRIBUTE_FAILED, ReplicateStatusModifier.WORKER); @@ -892,9 +976,7 @@ void shouldNotUpdateRunning2AllWorkersFailedSinceOneStillHasToBeLaunched() { // 1 replicates have tried to run the task and 1 is still to be run: // - R1 is in `COMPUTE_FAILED` status; // - R2 has not started yet. - Replicate replicate1 = new Replicate(); - replicate1.setWalletAddress(WALLET_WORKER_1); - replicate1.setChainTaskId(CHAIN_TASK_ID); + Replicate replicate1 = new Replicate(WALLET_WORKER_1, CHAIN_TASK_ID); replicate1.setStatusUpdateList(new ArrayList<>()); replicate1.updateStatus(ReplicateStatus.COMPUTE_FAILED, ReplicateStatusModifier.WORKER); diff --git a/src/test/java/com/iexec/core/task/update/TaskUpdateRequestManagerTests.java b/src/test/java/com/iexec/core/task/update/TaskUpdateRequestManagerTests.java index 6394de573..d8543b48e 100644 --- a/src/test/java/com/iexec/core/task/update/TaskUpdateRequestManagerTests.java +++ b/src/test/java/com/iexec/core/task/update/TaskUpdateRequestManagerTests.java @@ -119,7 +119,7 @@ void shouldNotUpdateAtTheSameTime() { .timeout(30, TimeUnit.SECONDS) .until(() -> callsOrder.size() == callsPerUpdate * updates.size()); - Assertions.assertThat(callsOrder.size()).isEqualTo(callsPerUpdate * updates.size()); + Assertions.assertThat(callsOrder).hasSize(callsPerUpdate * updates.size()); // We loop through calls order and see if all calls for a given update have finished // before another update starts for this task. diff --git a/src/test/java/com/iexec/core/utils/version/VersionServiceTest.java b/src/test/java/com/iexec/core/utils/version/VersionServiceTests.java similarity index 97% rename from src/test/java/com/iexec/core/utils/version/VersionServiceTest.java rename to src/test/java/com/iexec/core/utils/version/VersionServiceTests.java index 6b1589fed..ac7d1f8cb 100644 --- a/src/test/java/com/iexec/core/utils/version/VersionServiceTest.java +++ b/src/test/java/com/iexec/core/utils/version/VersionServiceTests.java @@ -11,7 +11,7 @@ import org.mockito.MockitoAnnotations; import org.springframework.boot.info.BuildProperties; -public class VersionServiceTest { +class VersionServiceTests { @Mock private BuildProperties buildProperties; diff --git a/src/test/java/com/iexec/core/worker/WorkerControllerTests.java b/src/test/java/com/iexec/core/worker/WorkerControllerTests.java index 7ec95570d..5ba24c968 100644 --- a/src/test/java/com/iexec/core/worker/WorkerControllerTests.java +++ b/src/test/java/com/iexec/core/worker/WorkerControllerTests.java @@ -14,9 +14,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.util.List; import java.util.Optional; @@ -162,10 +159,10 @@ void shouldNotGetChallengeSinceNotAllowedToJoin() { //region getToken @Test - void shouldGetToken() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + void shouldGetToken() { when(workerService.isAllowedToJoin(WALLET)).thenReturn(true); when(challengeService.getChallenge(WALLET)).thenReturn(CHALLENGE); - when(jwtTokenProvider.createToken(WALLET)).thenReturn(TOKEN); + when(jwtTokenProvider.getOrCreateToken(WALLET)).thenReturn(TOKEN); ResponseEntity response = workerController.getToken(WALLET, SIGN); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } diff --git a/src/test/java/com/iexec/core/worker/WorkerServiceTests.java b/src/test/java/com/iexec/core/worker/WorkerServiceTests.java index 68147f609..90067e8bb 100644 --- a/src/test/java/com/iexec/core/worker/WorkerServiceTests.java +++ b/src/test/java/com/iexec/core/worker/WorkerServiceTests.java @@ -65,8 +65,7 @@ void shouldGetWorker() { when(workerRepository.findByWalletAddress(walletAddress)).thenReturn(Optional.of(existingWorker)); Optional foundWorker = workerService.getWorker(walletAddress); - assertThat(foundWorker.isPresent()).isTrue(); - assertThat(foundWorker.get()).isEqualTo(existingWorker); + assertThat(foundWorker).contains(existingWorker); } // addWorker @@ -175,15 +174,15 @@ void shouldUpdateLastAlive() throws ParseException { Date now = new Date(); long duration = now.getTime() - argument.getValue().getLastAliveDate().getTime(); long diffInSeconds = TimeUnit.MILLISECONDS.toSeconds(duration); - assertThat(diffInSeconds).isEqualTo(0); + assertThat(diffInSeconds).isZero(); // check object returned by the method - assertThat(updatedWorker.isPresent()).isTrue(); + assertThat(updatedWorker).isPresent(); assertThat(updatedWorker.get().getId()).isEqualTo(worker.getId()); assertThat(updatedWorker.get().getName()).isEqualTo(worker.getName()); duration = now.getTime() - updatedWorker.get().getLastAliveDate().getTime(); diffInSeconds = TimeUnit.MILLISECONDS.toSeconds(duration); - assertThat(diffInSeconds).isEqualTo(0); + assertThat(diffInSeconds).isZero(); } @Test @@ -192,7 +191,7 @@ void shouldNotFindWorkerForUpdateLastAlive() { when(workerRepository.findByWalletAddress(walletAddress)).thenReturn(Optional.empty()); Optional optional = workerService.updateLastAlive(walletAddress); - assertThat(optional.isPresent()).isFalse(); + assertThat(optional).isEmpty(); } // isWorkerAllowedToAskReplicate @@ -270,11 +269,11 @@ void shouldAddTaskIdToWorker(){ when(workerRepository.save(existingWorker)).thenReturn(existingWorker); Optional addedWorker = workerService.addChainTaskIdToWorker("task3", walletAddress); - assertThat(addedWorker.isPresent()).isTrue(); + assertThat(addedWorker).isPresent(); Worker worker = addedWorker.get(); - assertThat(worker.getParticipatingChainTaskIds().size()).isEqualTo(3); + assertThat(worker.getParticipatingChainTaskIds()).hasSize(3); assertThat(worker.getParticipatingChainTaskIds().get(2)).isEqualTo("task3"); - assertThat(worker.getComputingChainTaskIds().size()).isEqualTo(3); + assertThat(worker.getComputingChainTaskIds()).hasSize(3); assertThat(worker.getComputingChainTaskIds().get(2)).isEqualTo("task3"); } @@ -282,7 +281,7 @@ void shouldAddTaskIdToWorker(){ void shouldNotAddTaskIdToWorker(){ when(workerRepository.findByWalletAddress(Mockito.anyString())).thenReturn(Optional.empty()); Optional addedWorker = workerService.addChainTaskIdToWorker("task1", "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"); - assertThat(addedWorker.isPresent()).isFalse(); + assertThat(addedWorker).isEmpty(); } // getChainTaskIds @@ -350,11 +349,11 @@ void shouldRemoveTaskIdFromWorker(){ when(workerRepository.save(existingWorker)).thenReturn(existingWorker); Optional removedWorker = workerService.removeChainTaskIdFromWorker("task2", walletAddress); - assertThat(removedWorker.isPresent()).isTrue(); + assertThat(removedWorker).isPresent(); Worker worker = removedWorker.get(); - assertThat(worker.getParticipatingChainTaskIds().size()).isEqualTo(1); + assertThat(worker.getParticipatingChainTaskIds()).hasSize(1); assertThat(worker.getParticipatingChainTaskIds().get(0)).isEqualTo("task1"); - assertThat(worker.getComputingChainTaskIds().size()).isEqualTo(1); + assertThat(worker.getComputingChainTaskIds()).hasSize(1); assertThat(worker.getComputingChainTaskIds().get(0)).isEqualTo("task1"); } @@ -362,7 +361,7 @@ void shouldRemoveTaskIdFromWorker(){ void shouldNotRemoveTaskIdWorkerNotFound(){ when(workerRepository.findByWalletAddress(Mockito.anyString())).thenReturn(Optional.empty()); Optional addedWorker = workerService.removeChainTaskIdFromWorker("task1", "0x1a69b2eb604db8eba185df03ea4f5288dcbbd248"); - assertThat(addedWorker.isPresent()).isFalse(); + assertThat(addedWorker).isEmpty(); } @Test @@ -387,12 +386,12 @@ void shouldNotRemoveAnythingSinceTaskIdNotFound(){ when(workerRepository.save(existingWorker)).thenReturn(existingWorker); Optional removedWorker = workerService.removeChainTaskIdFromWorker("dummyTaskId", walletAddress); - assertThat(removedWorker.isPresent()).isTrue(); + assertThat(removedWorker).isPresent(); Worker worker = removedWorker.get(); - assertThat(worker.getParticipatingChainTaskIds().size()).isEqualTo(2); + assertThat(worker.getParticipatingChainTaskIds()).hasSize(2); assertThat(worker.getParticipatingChainTaskIds()).isEqualTo(participatingIds); - assertThat(worker.getComputingChainTaskIds().size()).isEqualTo(2); + assertThat(worker.getComputingChainTaskIds()).hasSize(2); assertThat(worker.getComputingChainTaskIds()).isEqualTo(computingIds); } @@ -418,12 +417,12 @@ void shouldRemoveComputedChainTaskIdFromWorker(){ when(workerRepository.save(existingWorker)).thenReturn(existingWorker); Optional removedWorker = workerService.removeComputedChainTaskIdFromWorker("task1", walletAddress); - assertThat(removedWorker.isPresent()).isTrue(); + assertThat(removedWorker).isPresent(); Worker worker = removedWorker.get(); - assertThat(worker.getParticipatingChainTaskIds().size()).isEqualTo(2); + assertThat(worker.getParticipatingChainTaskIds()).hasSize(2); assertThat(worker.getParticipatingChainTaskIds()).isEqualTo(participatingIds); - assertThat(worker.getComputingChainTaskIds().size()).isEqualTo(1); + assertThat(worker.getComputingChainTaskIds()).hasSize(1); assertThat(worker.getComputingChainTaskIds().get(0)).isEqualTo("task2"); } @@ -449,7 +448,7 @@ void shouldNotRemoveComputedChainTaskIdFromWorkerSinceWorkerNotFound(){ when(workerRepository.save(existingWorker)).thenReturn(existingWorker); Optional removedWorker = workerService.removeComputedChainTaskIdFromWorker("task1", walletAddress); - assertThat(removedWorker.isPresent()).isFalse(); + assertThat(removedWorker).isEmpty(); } @Test @@ -474,12 +473,12 @@ void shouldNotRemoveComputedChainTaskIdFromWorkerSinceChainTaskIdNotFound(){ when(workerRepository.save(existingWorker)).thenReturn(existingWorker); Optional removedWorker = workerService.removeComputedChainTaskIdFromWorker("dummyTaskId", walletAddress); - assertThat(removedWorker.isPresent()).isTrue(); + assertThat(removedWorker).isPresent(); Worker worker = removedWorker.get(); - assertThat(worker.getParticipatingChainTaskIds().size()).isEqualTo(2); + assertThat(worker.getParticipatingChainTaskIds()).hasSize(2); assertThat(worker.getParticipatingChainTaskIds()).isEqualTo(participatingIds); - assertThat(worker.getComputingChainTaskIds().size()).isEqualTo(2); + assertThat(worker.getComputingChainTaskIds()).hasSize(2); assertThat(worker.getComputingChainTaskIds()).isEqualTo(computingIds); } @@ -500,8 +499,9 @@ void shouldGetLostWorkers() { assertThat(diffInMinutes).isEqualTo(1); // check the claimedLostWorkers are actually the lostWorkers - assertThat(claimedLostWorkers.size()).isEqualTo(2); - assertThat(claimedLostWorkers).isEqualTo(lostWorkers); + assertThat(claimedLostWorkers) + .hasSize(2) + .isEqualTo(lostWorkers); } @Test @@ -529,8 +529,9 @@ void shouldGetAliveWorkers() { assertThat(diffInMinutes).isEqualTo(1); // check the claimedAliveWorkers are actually the aliveWorkers - assertThat(claimedAliveWorkers.size()).isEqualTo(1); - assertThat(claimedAliveWorkers).isEqualTo(aliveWorkers); + assertThat(claimedAliveWorkers) + .hasSize(1) + .isEqualTo(aliveWorkers); } @Test @@ -631,13 +632,13 @@ void shouldGetZeroAvailableCpuIfWorkerAlreadyFull() { Arrays.asList("task1", "task2", "task3", "task4")); when(workerRepository.findByLastAliveDateAfter(any())).thenReturn(Arrays.asList(worker1, worker2)); - assertThat(workerService.getAliveAvailableCpu()).isEqualTo(0); + assertThat(workerService.getAliveAvailableCpu()).isZero(); } @Test void shouldGetZeroAvailableCpuIfNoWorkerAlive() { when(workerRepository.findByLastAliveDateAfter(any())).thenReturn(Collections.emptyList()); - assertThat(workerService.getAliveAvailableCpu()).isEqualTo(0); + assertThat(workerService.getAliveAvailableCpu()).isZero(); } // getAliveTotalCpu