diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..506a6679cf --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,69 @@ +name: Deploy Docker Image + +on: + push: + branches: + - 2021.x + paths: + - '**/pom.xml' + - '**/src/main/**' + pull_request: + branches: + - 2021.x + paths: + - '**/pom.xml' + - '**/src/main/**' + release: + types: + - published + workflow_dispatch: + +env: + MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.count=3 -Dmaven.wagon.httpconnectionManager.ttlSeconds=30 -DskipTests + +jobs: + deploy-docker-iamge: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v2 + + # setup docker + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # build target with maven + - name: Cache Maven Repos + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Set up JDK 8 + uses: actions/setup-java@v1 + with: + java-version: 8 + + - name: set environment + run: export MAVEN_OPTS=' -Dmaven.javadoc.skip=true -Drat.skip=true -Djacoco.skip=true $MAVEN_OPTS' + - name: Build Project + run: | + ./mvnw -B -Prelease,docker -DskipTests clean install + docker image ls --format "{{.ID}} {{.Repository}} {{.Tag}}" | grep alibaba| sed 's/apache\//${{ secrets.DOCKERHUB_USERNAME }}\//' |tr A-Z a-z |awk '{system("docker tag "$1" "$2":latest;docker tag "$1" "$2":"$3";")}' + + - name: Push Docker Image + run: | + echo Docker Images: + echo `docker image ls|grep -i ${{ secrets.DOCKERHUB_USERNAME }}|awk '{print $1":"$2}'` + docker image ls|grep -i ${{ secrets.DOCKERHUB_USERNAME }}|awk '{print $1":"$2}'|xargs -i docker push {} diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 804dab9dbb..ec9289c7c7 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -3,10 +3,49 @@ on: push: branches: - 2021.x + paths: + - '.github/workflows/integration-test.yml' + - '**/pom.xml' + - '**/src/main/**' pull_request: branches: - 2021.x + paths: + - '.github/workflows/integration-test.yml' + - '**/pom.xml' + - '**/src/main/**' jobs: + build-it-image: + name: build-it-image + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v2 + - name: Maven resolve ranges + run: ./mvnw versions:resolve-ranges -ntp -Dincludes='org.springframework:*,org.springframework.boot:*' + - name: Cache Maven Repos + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: spring-cloud-alibaba-it-cache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 8 + - name: Build IT image + run: ./mvnw -B clean install -am -pl spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers -Pit.env.docker -DskipTests -Dspotless.apply.skip=true + - name: Save IT image + run: docker save -o /tmp/spring-cloud-alibaba-testcontainers.tar apache/spring-cloud-alibaba-testcontainers:latest + - name: Upload IT image + uses: actions/upload-artifact@v3 + with: + name: it-image + path: /tmp/spring-cloud-alibaba-testcontainers.tar + retention-days: 1 + integration-testing: name: Integration Testing runs-on: ubuntu-latest @@ -29,4 +68,18 @@ jobs: - name: Testing run: mvn clean test # run: mvn clean -Dit.enabled=true test + - name: Download IT image + uses: actions/download-artifact@v3 + with: + name: it-image + path: /tmp/ + - name: Load IT image + run: docker load -i /tmp/spring-cloud-alibaba-testcontainers.tar + - name: "run install by skip tests" + if: ${{ steps.check_changes.outputs.docs_only != 'true' }} + run: mvn -q -B -ntp clean install -DskipTests + - name: run integration tests + if: ${{ steps.check_changes.outputs.docs_only != 'true' }} + run: ./build/run_integration_group.sh CLI + diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index a447c9fa81..472e16a5e3 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip \ No newline at end of file +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip \ No newline at end of file diff --git a/build/retry.sh b/build/retry.sh new file mode 100755 index 0000000000..17934ebdd9 --- /dev/null +++ b/build/retry.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +function fail { + echo $1 >&2 + exit 1 +} + +function retry { + local n=1 + local max=3 + local delay=10 + while true; do + "$@" && break || { + if [[ $n -lt $max ]]; then + ((n++)) + echo "Command failed. Attempt $n/$max:" + sleep $delay; + else + fail "The command has failed after $n attempts." + fi + } + done +} + +retry "$@" \ No newline at end of file diff --git a/build/run_integration_group.sh b/build/run_integration_group.sh new file mode 100755 index 0000000000..66ecfefd7e --- /dev/null +++ b/build/run_integration_group.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +set -e +set -o pipefail +set -o errexit + +TEST_GROUP=$1 +if [ -z "$TEST_GROUP" ]; then + echo "usage: $0 [test_group]" + exit 1 +fi +shift + +# runs integration tests +mvn_run_integration_test() { + ( + RETRY="" + # wrap with retry.sh script if next parameter is "--retry" + if [[ "$1" == "--retry" ]]; then + RETRY="./build/retry.sh" + shift + fi + # skip wrapping with retry.sh script if next parameter is "--no-retry" + if [[ "$1" == "--no-retry" ]]; then + RETRY="" + shift + fi + set -x + + + # run the integration tests + $RETRY ./mvnw -nsu -B install -f spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/pom.xml -Pit.env.docker test "$@" + ) +} diff --git a/mvnw b/mvnw index 761189a974..1f82e25902 100755 --- a/mvnw +++ b/mvnw @@ -36,219 +36,281 @@ if [ -z "$MAVEN_SKIP_RC" ] ; then - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # - # Look for the Apple JDKs first to preserve the existing behaviour, and then look - # for the new JDKs provided by Oracle. - # - if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then - # - # Apple JDKs - # - export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then - # - # Apple JDKs - # - export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then - # - # Oracle JDKs - # - export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home - fi - - if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then - # - # Apple JDKs - # - export JAVA_HOME=`/usr/libexec/java_home` - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Migwn, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - local basedir=$(pwd) - local wdir=$(pwd) - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - wdir=$(cd "$wdir/.."; pwd) - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -echo "Running version check" -VERSION=$( sed '\!//' -e 's!.*$!!' ) -echo "The found version is [${VERSION}]" - -if echo $VERSION | egrep -q 'M|RC'; then - echo Activating \"milestone\" profile for version=\"$VERSION\" - echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pmilestone" -else - echo Deactivating \"milestone\" profile for version=\"$VERSION\" - echo $MAVEN_ARGS | grep -q milestone && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pmilestone//') -fi - -if echo $VERSION | egrep -q 'RELEASE'; then - echo Activating \"central\" profile for version=\"$VERSION\" - echo $MAVEN_ARGS | grep -q milestone || MAVEN_ARGS="$MAVEN_ARGS -Pcentral" -else - echo Deactivating \"central\" profile for version=\"$VERSION\" - echo $MAVEN_ARGS | grep -q central && MAVEN_ARGS=$(echo $MAVEN_ARGS | sed -e 's/-Pcentral//') -fi - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Drevision=${VERSION}" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} ${MAVEN_ARGS} "$@" \ No newline at end of file + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + + fi + + # OS specific support. $var _must_ be set to either true or false. + cygwin=false; + darwin=false; + mingw=false + case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; + esac + + if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi + fi + + if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME + fi + + # For Cygwin, ensure paths are in UNIX format before anything is touched + if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` + fi + + # For Mingw, ensure paths are in UNIX format before anything is touched + if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + fi + + if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi + fi + + if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi + fi + + if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 + fi + + if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." + fi + + CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + + # traverses directory structure from process work directory to filesystem root + # first directory with .mvn subdirectory is considered project base directory + find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" + } + + # concatenates all lines of a file + concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi + } + + BASE_DIR=`find_maven_basedir "$(pwd)"` + if [ -z "$BASE_DIR" ]; then + exit 1; + fi + + ########################################################################################## + # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central + # This allows using the maven wrapper in projects that prohibit checking in binary data. + ########################################################################################## + if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi + fi + ########################################################################################## + # End of extension + ########################################################################################## + + export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} + if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR + fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + + # For Cygwin, switch paths to Windows format before running java + if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` + fi + + # Provide a "standardized" way to retrieve the CLI args that will + # work with both Windows and non-Windows executions. + MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" + export MAVEN_CMD_LINE_ARGS + + WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + + exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigManager.java b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigManager.java index e27b3e06b5..477e63a978 100644 --- a/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigManager.java +++ b/spring-cloud-alibaba-starters/spring-cloud-starter-alibaba-nacos-config/src/main/java/com/alibaba/cloud/nacos/NacosConfigManager.java @@ -46,7 +46,7 @@ public NacosConfigManager(NacosConfigProperties nacosConfigProperties) { /** * Compatible with old design,It will be perfected in the future. */ - static ConfigService createConfigService( + public static ConfigService createConfigService( NacosConfigProperties nacosConfigProperties) { if (Objects.isNull(service)) { synchronized (NacosConfigManager.class) { diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/pom.xml b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/pom.xml similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/pom.xml rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/pom.xml diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/main/java/com/alibaba/cloud/tests/nacos/config/NacosConfigTestApplication.java b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/main/java/com/alibaba/cloud/tests/nacos/config/NacosConfigTestApplication.java similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/main/java/com/alibaba/cloud/tests/nacos/config/NacosConfigTestApplication.java rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/main/java/com/alibaba/cloud/tests/nacos/config/NacosConfigTestApplication.java diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/main/java/com/alibaba/cloud/tests/nacos/config/UserProperties.java b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/main/java/com/alibaba/cloud/tests/nacos/config/UserProperties.java similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/main/java/com/alibaba/cloud/tests/nacos/config/UserProperties.java rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/main/java/com/alibaba/cloud/tests/nacos/config/UserProperties.java diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/main/resources/application.yml b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/main/resources/application.yml similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/main/resources/application.yml rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/main/resources/application.yml diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/test/java/com/alibaba/cloud/tests/nacos/config/NacosConfigRefreshTest.java b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/test/java/com/alibaba/cloud/tests/nacos/config/NacosConfigRefreshTest.java similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/test/java/com/alibaba/cloud/tests/nacos/config/NacosConfigRefreshTest.java rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/test/java/com/alibaba/cloud/tests/nacos/config/NacosConfigRefreshTest.java diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/test/java/com/alibaba/cloud/tests/nacos/config/NacosImportCheckTest.java b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/test/java/com/alibaba/cloud/tests/nacos/config/NacosImportCheckTest.java similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/test/java/com/alibaba/cloud/tests/nacos/config/NacosImportCheckTest.java rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/test/java/com/alibaba/cloud/tests/nacos/config/NacosImportCheckTest.java diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/test/resources/application-nacos-config-refresh.yml b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/test/resources/application-nacos-config-refresh.yml similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-config-test/src/test/resources/application-nacos-config-refresh.yml rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-config-test/src/test/resources/application-nacos-config-refresh.yml diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/pom.xml b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/pom.xml similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/pom.xml rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/pom.xml diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/main/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTestApp.java b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/src/main/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTestApp.java similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/main/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTestApp.java rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/src/main/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTestApp.java diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTest.java b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/src/test/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTest.java similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTest.java rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/src/test/java/com/alibaba/cloud/tests/nacos/discovery/NacosDiscoveryTest.java diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/resources/application-service-1.yml b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/src/test/resources/application-service-1.yml similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/resources/application-service-1.yml rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/src/test/resources/application-service-1.yml diff --git a/spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/resources/application-service-2.yml b/spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/src/test/resources/application-service-2.yml similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/nacos-discovery-test/src/test/resources/application-service-2.yml rename to spring-cloud-alibaba-tests/nacos-old-tests/nacos-discovery-test/src/test/resources/application-service-2.yml diff --git a/spring-cloud-alibaba-tests/nacos-tests/pom.xml b/spring-cloud-alibaba-tests/nacos-old-tests/pom.xml similarity index 100% rename from spring-cloud-alibaba-tests/nacos-tests/pom.xml rename to spring-cloud-alibaba-tests/nacos-old-tests/pom.xml diff --git a/spring-cloud-alibaba-tests/pom.xml b/spring-cloud-alibaba-tests/pom.xml index a71a35a814..20b7034f00 100644 --- a/spring-cloud-alibaba-tests/pom.xml +++ b/spring-cloud-alibaba-tests/pom.xml @@ -14,7 +14,7 @@ spring-cloud-alibaba-test-support - nacos-tests + spring-cloud-alibaba-testcontainers diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/ContainerStarter.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/ContainerStarter.java index febcc3832e..7b1793232f 100644 --- a/spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/ContainerStarter.java +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/ContainerStarter.java @@ -49,6 +49,7 @@ public class ContainerStarter { /** * Start Nacos container, using default version. + * @return a generic container */ public static GenericContainer startNacos() { return startNacos(NACOS_VERSION); @@ -56,6 +57,7 @@ public static GenericContainer startNacos() { /** * Start RocketMQ container, using default version. + * @return a generic container */ public static GenericContainer startRocketmq() { return startRocketmq(ROCKETMQ_VERSION); @@ -64,6 +66,7 @@ public static GenericContainer startRocketmq() { /** * Start Nacos container, using specific version. * @param version Nacos version + * @return a generic container */ public static GenericContainer startNacos(String version) { if (!nacosMap.containsKey(version)) { @@ -80,6 +83,7 @@ public static GenericContainer startNacos(String version) { /** * Start RocketMQ container, using specific version. * @param version RocketMQ version + * @return a generic container */ public static GenericContainer startRocketmq(String version) { if (!rocketmqMap.containsKey(version)) { diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/Tester.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/Tester.java index 4ca82655c9..dc4db28829 100644 --- a/spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/Tester.java +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-test-support/src/main/java/com/alibaba/cloud/testsupport/Tester.java @@ -19,7 +19,6 @@ /** * * @author freeman - * @date 2021.0.1.0 */ public class Tester { diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/assembly/docker-spring-cloud-assembly.xml b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/assembly/docker-spring-cloud-assembly.xml new file mode 100644 index 0000000000..326e8c05d5 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/assembly/docker-spring-cloud-assembly.xml @@ -0,0 +1,31 @@ + + spring-cloud-alibaba-testcontainers-bin + + tar.gz + + true + + + + src/test/assembly/bin + bin + 0755 + + + + + + ${project.build.directory}/${project.build.finalName}-tests.jar + lib + 0644 + + + + + + lib + 0644 + + + \ No newline at end of file diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/pom.xml b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/pom.xml new file mode 100644 index 0000000000..08c9530f2e --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/pom.xml @@ -0,0 +1,172 @@ + + + 4.0.0 + + com.alibaba.cloud + spring-cloud-alibaba-tests + ${revision} + + + spring-cloud-alibaba-testcontainers + spring-cloud-alibaba-testcontainers + spring-cloud-alibaba-testcontainers + pom + + + 1.7.25 + 1.17.3 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + pom + import + + + org.projectlombok + lombok + + + org.testcontainers + testcontainers + ${testcontainers.version} + + + org.assertj + assertj-core + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + com.alibaba.cloud + spring-cloud-starter-stream-rocketmq + ${revision} + + + + com.alibaba.cloud + spring-cloud-starter-bus-rocketmq + ${revision} + + + org.testng + testng + RELEASE + compile + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + ${project.version} + ${project.build.directory} + + true + + + + + + + + it.env.docker + + DOCKER + + + + + maven-assembly-plugin + + false + + src/test/assembly/docker-spring-cloud-assembly.xml + + + + + assembly + + single + + package + + + + + com.spotify + dockerfile-maven-plugin + + alibaba/spring-cloud-alibaba-testcontainers + ${project.version} + latest + + ${project.build.finalName} + + + + + spring-cloud-alibaba-testcontainers-bin + + build + + + + + + + + + skipIntegrationTests + + + skipIntegrationTests + + + + + + + diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/NacosConfig.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/NacosConfig.java new file mode 100644 index 0000000000..35933aef3b --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/NacosConfig.java @@ -0,0 +1,18 @@ +package com.alibaba.cloud.integration; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class NacosConfig { + + private String dataId; + + private String namespace; + + private String group; + + private String type; + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/UserProperties.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/UserProperties.java new file mode 100644 index 0000000000..2bd4733d67 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/UserProperties.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.cloud.integration; + +import java.util.List; +import java.util.Map; + +import lombok.Data; + +/** + * @author freeman + */ +@Data +public class UserProperties { + private String name; + private Integer age; + private Map map; + private List users; + + @Data + public static class User { + private String name; + private Integer age; + } + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/ChaosContainer.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/ChaosContainer.java new file mode 100644 index 0000000000..51fb193b1a --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/ChaosContainer.java @@ -0,0 +1,106 @@ +package com.alibaba.cloud.integration.common; + +import java.util.Base64; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import com.alibaba.cloud.integration.docker.ContainerExecResult; +import com.alibaba.cloud.integration.utils.DockerUtils; +import com.github.dockerjava.api.DockerClient; +import lombok.extern.slf4j.Slf4j; +import org.testcontainers.containers.GenericContainer; + +@Slf4j +public class ChaosContainer> + extends GenericContainer { + + protected final String clusterName; + + protected ChaosContainer(String clusterName, String image) { + super(image); + this.clusterName = clusterName; + } + + /** + * Initialize container startup parameters + */ + @Override + protected void configure() { + super.configure(); + addEnv("MALLOC_ARENA_MAX", "1"); + } + + /** + * Do some preprocessing before the container stops + */ + protected void beforeStop() { + if (null == getContainerId()) { + return; + } + + // dump the container log + DockerUtils.dumpContainerLogToTarget(getDockerClient(), getContainerId()); + } + + /** + * container stopped + */ + @Override + public void stop() { + beforeStop(); + super.stop(); + } + + protected void tailContainerLog() { + withLogConsumer(item -> log.info(item.getUtf8String())); + } + + public void putFile(String path, byte[] contents) throws Exception { + String base64contents = Base64.getEncoder().encodeToString(contents); + String cmd = String.format("echo %s | base64 -d > %s", base64contents, path); + execCmd("bash", "-c", cmd); + } + + public ContainerExecResult execCmd(String... commands) throws Exception { + DockerClient client = this.getDockerClient(); + String dockerId = this.getContainerId(); + return DockerUtils.runCommand(client, dockerId, commands); + } + + public CompletableFuture execCmdAsync(String... commands) + throws Exception { + DockerClient client = this.getDockerClient(); + String dockerId = this.getContainerId(); + return DockerUtils.runCommandAsync(client, dockerId, commands); + } + + public ContainerExecResult execCmdAsUser(String userId, String... commands) + throws Exception { + DockerClient client = this.getDockerClient(); + String dockerId = this.getContainerId(); + return DockerUtils.runCommandAsUser(userId, client, dockerId, commands); + } + + public CompletableFuture execCmdAsyncAsUser(String userId, + String... commands) throws Exception { + DockerClient client = this.getDockerClient(); + String dockerId = this.getContainerId(); + return DockerUtils.runCommandAsyncAsUser(userId, client, dockerId, commands); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ChaosContainer)) { + return false; + } + + ChaosContainer another = (ChaosContainer) o; + return clusterName.equals(another.clusterName) && super.equals(another); + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + Objects.hash(clusterName); + } + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/KeyValue.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/KeyValue.java new file mode 100644 index 0000000000..0413ef81b7 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/KeyValue.java @@ -0,0 +1,119 @@ +package com.alibaba.cloud.integration.common; + +import java.nio.ByteBuffer; +import java.util.Objects; + +public class KeyValue { + private final K key; + private final V value; + + public KeyValue(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * Encode a key and value pair into a bytes array. + * + * @param key key object to encode + * @param keyWriter a writer to encode key object + * @param value value object to encode + * @param valueWriter a writer to encode value object + * @return the encoded bytes array + */ + public static byte[] encode(K key, Schema keyWriter, V value, + Schema valueWriter) { + byte[] keyBytes; + if (key == null) { + keyBytes = new byte[0]; + } + else { + keyBytes = keyWriter.encode(key); + } + + byte[] valueBytes; + if (value == null) { + valueBytes = new byte[0]; + } + else { + valueBytes = valueWriter.encode(value); + } + ByteBuffer byteBuffer = ByteBuffer + .allocate(4 + keyBytes.length + 4 + valueBytes.length); + byteBuffer.putInt(key == null ? -1 : keyBytes.length).put(keyBytes) + .putInt(value == null ? -1 : valueBytes.length).put(valueBytes); + return byteBuffer.array(); + } + + /** + * Decode the value into a key/value pair. + * + * @param data the encoded bytes + * @param decoder the decoder to decode encoded key/value bytes + * @return the decoded key/value pair + */ + public static KeyValue decode(byte[] data, + KeyValueDecoder decoder) { + ByteBuffer byteBuffer = ByteBuffer.wrap(data); + int keyLength = byteBuffer.getInt(); + byte[] keyBytes = keyLength == -1 ? null : new byte[keyLength]; + if (keyBytes != null) { + byteBuffer.get(keyBytes); + } + + int valueLength = byteBuffer.getInt(); + byte[] valueBytes = valueLength == -1 ? null : new byte[valueLength]; + if (valueBytes != null) { + byteBuffer.get(valueBytes); + } + + return decoder.decode(keyBytes, valueBytes); + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof KeyValue)) { + return false; + } + KeyValue another = (KeyValue) obj; + return Objects.equals(key, another.key) && Objects.equals(value, another.value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("(key = \"").append(key).append("\", value = \"").append(value) + .append("\")"); + return sb.toString(); + } + + /** + * Decoder to decode key/value bytes. + */ + @FunctionalInterface + public interface KeyValueDecoder { + + /** + * Decode key and value bytes into a {@link KeyValue} pair. + * + * @param keyData key data + * @param valueData value data + * @return the decoded {@link KeyValue} pair + */ + KeyValue decode(byte[] keyData, byte[] valueData); + + } +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/NacosBootTester.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/NacosBootTester.java new file mode 100644 index 0000000000..7d7ef294c5 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/NacosBootTester.java @@ -0,0 +1,37 @@ +package com.alibaba.cloud.integration.common; + +import com.alibaba.cloud.integration.NacosConfig; +import com.alibaba.cloud.integration.UserProperties; +import com.alibaba.cloud.nacos.NacosConfigManager; +import com.alibaba.cloud.nacos.NacosConfigProperties; +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.exception.NacosException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; + +public abstract class NacosBootTester { + + private final Logger logger = LoggerFactory.getLogger(NacosBootTester.class); + + public void vaildateUpdateState(NacosConfig nacosConfig, + NacosConfigProperties properties, UserProperties userProperties) + throws NacosException { + + ConfigService configService = NacosConfigManager.createConfigService(properties); + String content = uploadFile(userProperties); + try { + configService.publishConfig(nacosConfig.getDataId(), nacosConfig.getGroup(), + content, nacosConfig.getType()); + } + finally { + logger.info("nacos 配置文件已经上传完成"); + } + } + + /** + * nacos update yaml + */ + protected abstract String uploadFile(UserProperties userProperties); + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/RocketmqSourceTester.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/RocketmqSourceTester.java new file mode 100644 index 0000000000..0dd0c3d728 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/RocketmqSourceTester.java @@ -0,0 +1,171 @@ +package com.alibaba.cloud.integration.common; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.shaded.com.google.common.collect.Maps; + +@Getter +@Slf4j +public abstract class RocketmqSourceTester { + + public static final String INSERT = "INSERT"; + + public static final String DELETE = "DELETE"; + + public static final String UPDATE = "UPDATE"; + public static final Set DEBEZIUM_FIELD_SET = new HashSet() { + { + add("before"); + add("after"); + add("source"); + add("op"); + add("ts_ms"); + add("transaction"); + } + }; + protected final String sourceType; + protected final Map sourceConfig; + protected int numEntriesToInsert = 1; + protected int numEntriesExpectAfterStart = 9; + + protected RocketmqSourceTester(String sourceType) { + this.sourceType = sourceType; + this.sourceConfig = Maps.newHashMap(); + } + + public abstract void setServiceContainer(ServiceContainerT serviceContainer); + + public String sourceType() { + return sourceType; + } + + public Map sourceConfig() { + return sourceConfig; + } + + public abstract void prepareSource() throws Exception; + + public abstract void prepareInsertEvent() throws Exception; + + public abstract void prepareDeleteEvent() throws Exception; + + public abstract void prepareUpdateEvent() throws Exception; + + public abstract Map produceSourceMessages(int numMessages) + throws Exception; + + public void validateSourceResult(Consumer consumer, int number, String eventType, + String converterClassName) throws Exception { + doPreValidationCheck(eventType); + if (converterClassName.endsWith("AvroConverter")) { + validateSourceResultAvro(consumer, number, eventType); + } + else { + validateSourceResultJson(consumer, number, eventType); + } + doPostValidationCheck(eventType); + } + + /** + * Execute before regular validation to check database-specific state. + */ + public void doPreValidationCheck(String eventType) { + log.info("pre-validation of {}", eventType); + } + + /** + * Execute after regular validation to check database-specific state. + */ + public void doPostValidationCheck(String eventType) { + log.info("post-validation of {}", eventType); + } + + public void validateSourceResultJson(Consumer> consumer, + int number, String eventType) throws Exception { + int recordsNumber = 0; + // Message> msg = + // consumer.receive(initialDelayForMsgReceive(), TimeUnit.SECONDS); + // while(msg != null) { + // recordsNumber ++; + // final String key = new String(msg.getValue().getKey()); + // final String value = new String(msg.getValue().getValue()); + // log.info("Received message: key = {}, value = {}.", key, value); + // Assert.assertTrue(key.contains(this.keyContains())); + // Assert.assertTrue(value.contains(this.valueContains())); + // if (eventType != null) { + // Assert.assertTrue(value.contains(this.eventContains(eventType, true))); + // } + // consumer.acknowledge(msg); + // msg = consumer.receive(1, TimeUnit.SECONDS); + // } + // + // Assert.assertEquals(recordsNumber, number); + // log.info("Stop {} server container. topic: {} has {} records.", + // getSourceType(), consumer.getTopic(), recordsNumber); + } + + public void validateSourceResultAvro(Consumer> consumer, + int number, String eventType) throws Exception { + int recordsNumber = 0; + // Message> msg = consumer.receive(initialDelayForMsgReceive(), + // TimeUnit.SECONDS); + // while(msg != null) { + // recordsNumber ++; + // K keyRecord = msg.getValue().getKey(); + // Assert.assertNotNull(keyRecord.getFields()); + // Assert.assertTrue(keyRecord.getFields().size() > 0); + // + // V valueRecord = msg.getValue().getValue(); + // Assert.assertNotNull(valueRecord.getFields()); + // Assert.assertTrue(valueRecord.getFields().size() > 0); + // + // log.info("Received message: key = {}, value = {}.", + // keyRecord.getNativeObject(), valueRecord.getNativeObject()); + // for (Field field : valueRecord.getFields()) { + // log.info("validating field {}", field.getName()); + // Assert.assertTrue(DEBEZIUM_FIELD_SET.contains(field.getName())); + // } + // + // if (eventType != null) { + // String op = valueRecord.getField("op").toString(); + // Assert.assertEquals(this.eventContains(eventType, false), op); + // } + // consumer.acknowledge(msg); + // msg = consumer.receive(1, TimeUnit.SECONDS); + // } + // + // Assert.assertEquals(recordsNumber, number); + // log.info("Stop {} server container. topic: {} has {} records.", + // getSourceType(), consumer.getTopic(), recordsNumber); + } + + public int initialDelayForMsgReceive() { + return 2; + } + + public String keyContains() { + return "dbserver1.inventory.products.Key"; + } + + public String valueContains() { + return "dbserver1.inventory.products.Value"; + } + + public String eventContains(String eventType, boolean isJson) { + if (eventType.equals(INSERT)) { + return isJson ? "\"op\":\"c\"" : "c"; + } + else if (eventType.equals(UPDATE)) { + return isJson ? "\"op\":\"u\"" : "u"; + } + else { + return isJson ? "\"op\":\"d\"" : "d"; + } + } +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/Schema.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/Schema.java new file mode 100644 index 0000000000..6926c073f6 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/Schema.java @@ -0,0 +1,97 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + *

+ * 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.alibaba.cloud.integration.common; + +/** + * Message schema definition. + */ +public interface Schema extends Cloneable { + + /** + * Check if the message is a valid object for this schema. + * + *

+ * The implementation can choose what its most efficient approach to validate the + * schema. If the implementation doesn't provide it, it will attempt to use + * {@link #decode(byte[])} to see if this schema can decode this message or not as a + * validation mechanism to verify the bytes. + * + * @param message the messages to verify + * @throws SchemaSerializationException if it is not a valid message + */ + default void validate(byte[] message) { + decode(message); + } + + /** + * Encode an object representing the message content into a byte array. + * + * @param message the message object + * @return a byte array with the serialized content + * @throws SchemaSerializationException if the serialization fails + */ + byte[] encode(T message); + + /** + * Returns whether this schema supports versioning. + * + *

+ * Most of the schema implementations don't really support schema versioning, or it + * just doesn't make any sense to support schema versionings (e.g. primitive schemas). + * Only schema returns {@link GenericRecord} should support schema versioning. + * + *

+ * If a schema implementation returns false, it should implement + * {@link #decode(byte[])}; while a schema implementation returns true, it + * should implement {@link #decode(byte[], byte[])} instead. + * + * @return true if this schema implementation supports schema versioning; otherwise + * returns false. + */ + default boolean supportSchemaVersioning() { + return false; + } + + /** + * Decode a byte array into an object using the schema definition and deserializer + * implementation. + * + * @param bytes the byte array to decode + * @return the deserialized object + */ + default T decode(byte[] bytes) { + // use `null` to indicate ignoring schema version + return decode(bytes, null); + } + + /** + * Decode a byte array into an object using a given version. + * + * @param bytes the byte array to decode + * @param schemaVersion the schema version to decode the object. null indicates using + * latest version. + * @return the deserialized object + */ + default T decode(byte[] bytes, byte[] schemaVersion) { + // ignore version by default (most of the primitive schema implementations ignore + // schema version) + return decode(bytes); + } + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/Const.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/Const.java new file mode 100644 index 0000000000..dec95c3cf7 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/Const.java @@ -0,0 +1,13 @@ +package com.alibaba.cloud.integration.common.nacos; + +public class Const { + + public static final Integer NACOS_SERVER_PORT = 8848; + public static final Integer LOCAL_SERVER_PORT = 8849; + public static final String DEFAULT_IMAGE_NAME = System.getenv() + .getOrDefault("TEST_IMAGE_NAME", "nacos-server-test"); + private static final String CONFIG_INSTANCE_PATH = "/nacos/v1/ns"; + public static final String NACOS_SERVER_URL = "http://127.0.0.1:" + NACOS_SERVER_PORT + + CONFIG_INSTANCE_PATH; + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/Params.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/Params.java new file mode 100644 index 0000000000..e02c36f2bd --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/Params.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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.alibaba.cloud.integration.common.nacos; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +public class Params { + + private MultiValueMap paramMap; + + public static Params newParams() { + Params params = new Params(); + params.paramMap = new LinkedMultiValueMap(); + return params; + } + + public Params appendParam(String name, String value) { + this.paramMap.add(name, value); + return this; + } + + public MultiValueMap done() { + return paramMap; + } +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/TextChangeParser.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/TextChangeParser.java new file mode 100644 index 0000000000..c1fd34e89f --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/common/nacos/TextChangeParser.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.alibaba.cloud.integration.common.nacos; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.nacos.api.config.ConfigChangeItem; +import com.alibaba.nacos.api.config.PropertyChangeType; +import com.alibaba.nacos.api.config.listener.ConfigChangeParser; + +public class TextChangeParser implements ConfigChangeParser { + @Override + public boolean isResponsibleFor(String type) { + return (null == type || "text".equalsIgnoreCase(type)); + } + + @Override + public Map doParse(String oldContent, String newContent, + String type) throws IOException { + Map map = new HashMap<>(4); + final String key = "content"; + + ConfigChangeItem cci = new ConfigChangeItem(key, oldContent, newContent); + if (null == oldContent && null != newContent) { + cci.setType(PropertyChangeType.ADDED); + } + else if (null != oldContent && null != newContent + && !oldContent.equals(newContent)) { + cci.setType(PropertyChangeType.MODIFIED); + } + else if (null != oldContent && null == newContent) { + cci.setType(PropertyChangeType.DELETED); + } + map.put(key, cci); + + return map; + } +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecException.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecException.java new file mode 100644 index 0000000000..010a749b50 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecException.java @@ -0,0 +1,34 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + *

+ * 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.alibaba.cloud.integration.docker; + +public class ContainerExecException extends Exception { + /** + * + * @param cmd command + * @param containerId container id + * @param result exec result + */ + public ContainerExecException(final String cmd, + final String containerId, final ContainerExecResult result) { + super(String.format("%s failed on %s with error code %d", + cmd, containerId, + result.getExitCode())); + } +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecResult.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecResult.java new file mode 100644 index 0000000000..ee22ad7ccd --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecResult.java @@ -0,0 +1,14 @@ +package com.alibaba.cloud.integration.docker; + +import lombok.Data; + +@Data(staticConstructor = "of") +public class ContainerExecResult { + + /** exception exit code.**/ + private final int exitCode; + /**container log stdout. **/ + private final String stdout; + /**container log stderr output. **/ + private final String stderr; +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecResultBytes.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecResultBytes.java new file mode 100644 index 0000000000..1a5a748c6c --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/docker/ContainerExecResultBytes.java @@ -0,0 +1,14 @@ +package com.alibaba.cloud.integration.docker; + +import lombok.Data; + +@Data(staticConstructor = "of") +public class ContainerExecResultBytes { + + /** exception exit code.**/ + private final int exitCode; + /**container log stdout. **/ + private final byte[] stdout; + /**container log stderr output. **/ + private final byte[] stderr; +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/utils/DockerUtils.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/utils/DockerUtils.java new file mode 100644 index 0000000000..0e36f53130 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/java/com/alibaba/cloud/integration/utils/DockerUtils.java @@ -0,0 +1,428 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + *

+ * 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.alibaba.cloud.integration.utils; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.zip.GZIPOutputStream; + +import com.alibaba.cloud.integration.docker.ContainerExecException; +import com.alibaba.cloud.integration.docker.ContainerExecResult; +import com.alibaba.cloud.integration.docker.ContainerExecResultBytes; +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.InspectContainerResponse; +import com.github.dockerjava.api.command.InspectExecResponse; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.model.ContainerNetwork; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.api.model.StreamType; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.utils.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public final class DockerUtils { + private static final Logger LOG = LoggerFactory.getLogger(DockerUtils.class); + + private DockerUtils() { + + } + + private static File getTargetDirectory(final String containerId) { + String base = System.getProperty("maven.buildDirectory"); + if (base == null) { + base = "target"; + } + File directory = new File(base + "/container-logs/" + containerId); + if (!directory.exists() && !directory.mkdirs()) { + LOG.error("Error creating directory for container logs."); + } + return directory; + } + + public static void dumpContainerLogToTarget(DockerClient dockerClient, + String containerId) { + final String containerName = getContainerName(dockerClient, containerId); + File output = getUniqueFileInTargetDirectory(containerName, "docker", ".log"); + try (OutputStream os = new BufferedOutputStream(new FileOutputStream(output))) { + CompletableFuture future = new CompletableFuture<>(); + dockerClient.logContainerCmd(containerName).withStdOut(true).withStdErr(true) + .withTimestamps(true).exec(new ResultCallback() { + @Override + public void close() { + } + + @Override + public void onStart(Closeable closeable) { + } + + @Override + public void onNext(Frame object) { + try { + os.write(object.getPayload()); + } + catch (IOException e) { + onError(e); + } + } + + @Override + public void onError(Throwable throwable) { + future.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + future.complete(true); + } + }); + future.get(); + } + catch (RuntimeException | ExecutionException | IOException e) { + LOG.error("Error dumping log for {}", containerName, e); + } + catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + LOG.info("Interrupted dumping log from container {}", containerName, ie); + } + } + + private static File getUniqueFileInTargetDirectory(String containerName, + String prefix, String suffix) { + return getUniqueFileInDirectory(getTargetDirectory(containerName), prefix, + suffix); + } + + private static File getUniqueFileInDirectory(File directory, String prefix, + String suffix) { + File file = new File(directory, prefix + suffix); + int i = 0; + while (file.exists()) { + LOG.info("{} exists, incrementing", file); + file = new File(directory, prefix + "_" + (i++) + suffix); + } + return file; + } + + private static String getContainerName(DockerClient dockerClient, + String containerId) { + final InspectContainerResponse inspectContainerResponse = dockerClient + .inspectContainerCmd(containerId).exec(); + // docker api returns names prefixed with "/", it's part of it's legacy design, + // this removes it to be consistent with what docker ps shows. + return inspectContainerResponse.getName().replace("/", ""); + } + + public static void dumpContainerDirToTargetCompressed(DockerClient dockerClient, + String containerId, String path) { + final String containerName = getContainerName(dockerClient, containerId); + final String baseName = path.replace("/", "-").replaceAll("^-", ""); + File output = getUniqueFileInTargetDirectory(containerName, baseName, ".tar.gz"); + try (InputStream dockerStream = dockerClient + .copyArchiveFromContainerCmd(containerId, path).exec(); + OutputStream os = new GZIPOutputStream( + new BufferedOutputStream(new FileOutputStream(output)))) { + IOUtils.copy(dockerStream, os); + } + catch (RuntimeException | IOException e) { + if (!(e instanceof NotFoundException)) { + LOG.error("Error reading dir from container {}", containerName, e); + } + } + } + + public static void dumpContainerLogDirToTarget(DockerClient docker, + String containerId, String path) { + File targetDirectory = getTargetDirectory(containerId); + try (InputStream dockerStream = docker + .copyArchiveFromContainerCmd(containerId, path).exec(); + TarArchiveInputStream stream = new TarArchiveInputStream(dockerStream)) { + TarArchiveEntry entry = stream.getNextTarEntry(); + while (entry != null) { + if (entry.isFile()) { + File output = new File(targetDirectory, + entry.getName().replace("/", "-")); + Files.copy(stream, output.toPath(), + StandardCopyOption.REPLACE_EXISTING); + } + entry = stream.getNextTarEntry(); + } + } + catch (RuntimeException | IOException e) { + LOG.error("Error reading logs from container {}", containerId, e); + } + } + + public static String getContainerIP(DockerClient docker, String containerId) { + for (Map.Entry e : docker + .inspectContainerCmd(containerId).exec().getNetworkSettings() + .getNetworks().entrySet()) { + return e.getValue().getIpAddress(); + } + throw new IllegalArgumentException( + "Container " + containerId + " has no networks"); + } + + public static ContainerExecResult runCommand(DockerClient docker, String containerId, + String... cmd) + throws ContainerExecException, ExecutionException, InterruptedException { + try { + return runCommandAsync(docker, containerId, cmd).get(); + } + catch (ExecutionException e) { + if (e.getCause() instanceof ContainerExecException) { + throw (ContainerExecException) e.getCause(); + } + throw e; + } + } + + public static ContainerExecResult runCommandAsUser(String userId, DockerClient docker, + String containerId, String... cmd) + throws ContainerExecException, ExecutionException, InterruptedException { + try { + return runCommandAsyncAsUser(userId, docker, containerId, cmd).get(); + } + catch (ExecutionException e) { + if (e.getCause() instanceof ContainerExecException) { + throw (ContainerExecException) e.getCause(); + } + throw e; + } + } + + public static CompletableFuture runCommandAsyncAsUser( + String userId, DockerClient dockerClient, String containerId, String... cmd) { + String execId = dockerClient.execCreateCmd(containerId).withCmd(cmd) + .withAttachStderr(true).withAttachStdout(true).withUser(userId).exec() + .getId(); + return runCommandAsync(execId, dockerClient, containerId, cmd); + } + + public static CompletableFuture runCommandAsync( + DockerClient dockerClient, String containerId, String... cmd) { + String execId = dockerClient.execCreateCmd(containerId).withCmd(cmd) + .withAttachStderr(true).withAttachStdout(true).exec().getId(); + return runCommandAsync(execId, dockerClient, containerId, cmd); + } + + private static CompletableFuture runCommandAsync(String execId, + DockerClient dockerClient, String containerId, String... cmd) { + CompletableFuture future = new CompletableFuture<>(); + final String containerName = getContainerName(dockerClient, containerId); + String cmdString = String.join(" ", cmd); + StringBuilder stdout = new StringBuilder(); + StringBuilder stderr = new StringBuilder(); + dockerClient.execStartCmd(execId).withDetach(false) + .exec(new ResultCallback() { + @Override + public void close() { + } + + @Override + public void onStart(Closeable closeable) { + LOG.info("DOCKER.exec({}:{}): Executing...", containerName, + cmdString); + } + + @Override + public void onNext(Frame object) { + LOG.info("DOCKER.exec({}:{}): {}", containerName, cmdString, + object); + if (StreamType.STDOUT == object.getStreamType()) { + stdout.append(new String(object.getPayload(), UTF_8)); + } + else if (StreamType.STDERR == object.getStreamType()) { + stderr.append(new String(object.getPayload(), UTF_8)); + } + } + + @Override + public void onError(Throwable throwable) { + future.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + LOG.info("DOCKER.exec({}:{}): Done", containerName, cmdString); + + InspectExecResponse resp = waitForExecCmdToFinish(dockerClient, + execId); + int retCode = resp.getExitCode(); + ContainerExecResult result = ContainerExecResult.of(retCode, + stdout.toString(), stderr.toString()); + LOG.info("DOCKER.exec({}:{}): completed with {}", containerName, + cmdString, retCode); + + if (retCode != 0) { + LOG.error( + "DOCKER.exec({}:{}): completed with non zero return code: {}\nstdout: {}\nstderr:" + + " {}", + containerName, cmdString, result.getExitCode(), + result.getStdout(), result.getStderr()); + future.completeExceptionally(new ContainerExecException( + cmdString, containerId, result)); + } + else { + future.complete(result); + } + } + }); + return future; + } + + public static ContainerExecResultBytes runCommandWithRawOutput( + DockerClient dockerClient, String containerId, String... cmd) + throws ContainerExecException { + CompletableFuture future = new CompletableFuture<>(); + String execId = dockerClient.execCreateCmd(containerId).withCmd(cmd) + .withAttachStderr(true).withAttachStdout(true).exec().getId(); + final String containerName = getContainerName(dockerClient, containerId); + String cmdString = String.join(" ", cmd); + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + dockerClient.execStartCmd(execId).withDetach(false) + .exec(new ResultCallback() { + @Override + public void close() { + } + + @Override + public void onStart(Closeable closeable) { + LOG.info("DOCKER.exec({}:{}): Executing...", containerName, + cmdString); + } + + @Override + public void onNext(Frame object) { + try { + if (StreamType.STDOUT == object.getStreamType()) { + stdout.write(object.getPayload()); + } + else if (StreamType.STDERR == object.getStreamType()) { + stderr.write(object.getPayload()); + } + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void onError(Throwable throwable) { + future.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + LOG.info("DOCKER.exec({}:{}): Done", containerName, cmdString); + future.complete(true); + } + }); + future.join(); + + InspectExecResponse resp = waitForExecCmdToFinish(dockerClient, execId); + int retCode = resp.getExitCode(); + + ContainerExecResultBytes result = ContainerExecResultBytes.of(retCode, + stdout.toByteArray(), stderr.toByteArray()); + LOG.info("DOCKER.exec({}:{}): completed with {}", containerName, cmdString, + retCode); + + if (retCode != 0) { + throw new ContainerExecException(cmdString, containerId, null); + } + return result; + } + + public static CompletableFuture runCommandAsyncWithLogging( + DockerClient dockerClient, String containerId, String... cmd) { + CompletableFuture future = new CompletableFuture<>(); + String execId = dockerClient.execCreateCmd(containerId).withCmd(cmd) + .withAttachStderr(true).withAttachStdout(true).exec().getId(); + final String containerName = getContainerName(dockerClient, containerId); + String cmdString = String.join(" ", cmd); + dockerClient.execStartCmd(execId).withDetach(false) + .exec(new ResultCallback() { + @Override + public void close() { + } + + @Override + public void onStart(Closeable closeable) { + LOG.info("DOCKER.exec({}:{}): Executing...", containerName, + cmdString); + } + + @Override + public void onNext(Frame object) { + LOG.info("DOCKER.exec({}:{}): {}", containerName, cmdString, + object); + } + + @Override + public void onError(Throwable throwable) { + future.completeExceptionally(throwable); + } + + @Override + public void onComplete() { + LOG.info("DOCKER.exec({}:{}): Done", containerName, cmdString); + InspectExecResponse resp = waitForExecCmdToFinish(dockerClient, + execId); + int retCode = resp.getExitCode(); + LOG.info("DOCKER.exec({}:{}): completed with {}", containerName, + cmdString, retCode); + future.complete(retCode); + } + }); + return future; + } + + private static InspectExecResponse waitForExecCmdToFinish(DockerClient dockerClient, + String execId) { + InspectExecResponse resp = dockerClient.inspectExecCmd(execId).exec(); + while (resp.isRunning()) { + try { + Thread.sleep(200); + } + catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ie); + } + resp = dockerClient.inspectExecCmd(execId).exec(); + } + return resp; + } + + public static Optional getContainerCluster(DockerClient docker, + String containerId) { + return Optional.ofNullable(docker.inspectContainerCmd(containerId).exec() + .getConfig().getLabels().get("cluster")); + } +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/resources/application.properties b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/resources/application.properties new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/assembly/docker-spring-cloud-assembly.xml b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/assembly/docker-spring-cloud-assembly.xml new file mode 100644 index 0000000000..326e8c05d5 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/assembly/docker-spring-cloud-assembly.xml @@ -0,0 +1,31 @@ + + spring-cloud-alibaba-testcontainers-bin + + tar.gz + + true + + + + src/test/assembly/bin + bin + 0755 + + + + + + ${project.build.directory}/${project.build.finalName}-tests.jar + lib + 0644 + + + + + + lib + 0644 + + + \ No newline at end of file diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/NacosContainer.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/NacosContainer.java new file mode 100644 index 0000000000..25b81721ef --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/NacosContainer.java @@ -0,0 +1,48 @@ +package com.alibaba.cloud.integration.test.nacos; + +import com.alibaba.cloud.integration.common.ChaosContainer; +import com.github.dockerjava.api.model.ExposedPort; +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.PortBinding; +import com.github.dockerjava.api.model.Ports; + +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.cloud.integration.common.nacos.Const.LOCAL_SERVER_PORT; +import static com.alibaba.cloud.integration.common.nacos.Const.NACOS_SERVER_PORT; +import static java.lang.String.format; + +public class NacosContainer> + extends ChaosContainer { + + public static final String DEFAULT_IMAGE_NAME = System.getenv() + .getOrDefault("TEST_IMAGE_NAME", "nacos"); + + public NacosContainer(String clusterName, String image) { + super(clusterName, image); + Map envKey = new HashMap<>(); + envKey.put("MODE", "standalone"); + withEnv(envKey) + .withCommand( + format("/bin/bash -c ' { + createContainerCmd.withHostName(DEFAULT_IMAGE_NAME); + createContainerCmd.withName(clusterName + "-" + DEFAULT_IMAGE_NAME); + createContainerCmd.withHostConfig(new HostConfig().withPortBindings( + new PortBinding(Ports.Binding.bindPort(NACOS_SERVER_PORT), new ExposedPort(LOCAL_SERVER_PORT)))); + }); + } + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/NacosContainerTest.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/NacosContainerTest.java new file mode 100644 index 0000000000..4d7a7fd340 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/NacosContainerTest.java @@ -0,0 +1,65 @@ +package com.alibaba.cloud.integration.test.nacos; + +import com.alibaba.cloud.integration.NacosConfig; +import com.alibaba.cloud.integration.UserProperties; +import com.alibaba.cloud.integration.common.NacosBootTester; +import com.alibaba.cloud.nacos.NacosConfigProperties; +import com.alibaba.nacos.api.exception.NacosException; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +@Slf4j +public class NacosContainerTest extends NacosBootTester { + + private static final String image = "freemanlau/nacos:1.4.2"; + private NacosContainer nacosContainer; + + @Override + public void vaildateUpdateState(NacosConfig nacosConfig, + NacosConfigProperties properties, UserProperties userProperties) + throws NacosException { + super.vaildateUpdateState(nacosConfig, properties, userProperties); + } + + @Override + protected String uploadFile(UserProperties userProperties) { + + try { + + String content = "configdata:\n" + " user:\n" + " age: 22\n" + + " name: freeman1123\n" + " map:\n" + " hobbies:\n" + + " - art\n" + " - programming\n" + " - movie\n" + + " intro: Hello, I'm freeman\n" + " extra: yo~\n" + + " users:\n" + " - name: dad\n" + " age: 20\n" + + " - name: mom\n" + " age: 18"; + return content; + } + catch (Exception ex) { + log.error("Nacos pulish failed"); + return null; + } + } + + @Before + public void setUp() throws Exception { + nacosContainer = new NacosContainer("nacos-example", image); + nacosContainer.start(); + } + + @After + public void cleanup() throws Exception { + + } + + @Test + public void testNacosStartUp() throws Exception { + NacosConfigProperties nacosConfigProperties = new NacosConfigProperties(); + UserProperties userProperties = new UserProperties(); + + NacosConfig nacosConfig = NacosConfig.builder().build(); + vaildateUpdateState(nacosConfig, nacosConfigProperties, userProperties); + } + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/common/NacosAsyncRestTemplateITCase.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/common/NacosAsyncRestTemplateITCase.java new file mode 100644 index 0000000000..52fe33008d --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/common/NacosAsyncRestTemplateITCase.java @@ -0,0 +1,162 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.alibaba.cloud.integration.test.nacos.common; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.cloud.integration.test.nacos.NacosContainer; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.http.Callback; +import com.alibaba.nacos.common.http.HttpClientBeanHolder; +import com.alibaba.nacos.common.http.HttpRestResult; +import com.alibaba.nacos.common.http.client.NacosAsyncRestTemplate; +import com.alibaba.nacos.common.http.param.Header; +import com.alibaba.nacos.common.http.param.Query; +import com.alibaba.nacos.common.model.RestResult; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import static com.alibaba.cloud.integration.common.nacos.Const.NACOS_SERVER_URL; + +public class NacosAsyncRestTemplateITCase { + + private static final String image = "nacos/nacos-server:2.0.2"; + private final NacosAsyncRestTemplate nacosRestTemplate = HttpClientBeanHolder + .getNacosAsyncRestTemplate( + LoggerFactory.getLogger(NacosAsyncRestTemplateITCase.class)); + private NacosContainer nacosContainer; + + @Before + public void setUp() throws NacosException { + nacosContainer = new NacosContainer("nacos-example", image); + nacosContainer.start(); + } + + @Test + public void test_url_post_form() throws Exception { + + String url = NACOS_SERVER_URL + "/instance"; + Map param = new HashMap<>(); + param.put("serviceName", "app-test"); + param.put("port", "8080"); + param.put("ip", "127.0.0.1"); + CallbackMap callbackMap = new CallbackMap<>(); + nacosRestTemplate.postForm(url, Header.newInstance(), Query.newInstance(), param, + String.class, callbackMap); + Thread.sleep(2000); + HttpRestResult restResult = callbackMap.getRestResult(); + System.out.println(restResult.getData()); + System.out.println(restResult.getHeader()); + Assert.assertTrue(restResult.ok()); + } + + @Test + public void test_url_put_form() throws Exception { + String url = NACOS_SERVER_URL + "/instance"; + Map param = new HashMap<>(); + param.put("serviceName", "app-test-change"); + param.put("port", "8080"); + param.put("ip", "11.11.11.11"); + CallbackMap callbackMap = new CallbackMap<>(); + nacosRestTemplate.postForm(url, Header.newInstance(), Query.newInstance(), param, + String.class, callbackMap); + Thread.sleep(2000); + HttpRestResult restResult = callbackMap.getRestResult(); + System.out.println(restResult.getData()); + System.out.println(restResult.getHeader()); + Assert.assertTrue(restResult.ok()); + } + + @Test + public void test_url_get() throws Exception { + String url = NACOS_SERVER_URL + "/instance/list"; + Query query = Query.newInstance().addParam("serviceName", "app-test"); + CallbackMap callbackMap = new CallbackMap<>(); + nacosRestTemplate.get(url, Header.newInstance(), query, Map.class, callbackMap); + Thread.sleep(2000); + HttpRestResult restResult = callbackMap.getRestResult(); + System.out.println(restResult.getData()); + System.out.println(restResult.getHeader()); + Assert.assertTrue(restResult.ok()); + Assert.assertEquals(restResult.getData().get("name"), "DEFAULT_GROUP@@app-test"); + } + + @Test + public void test_url_by_map() throws Exception { + String url = NACOS_SERVER_URL + "/instance/list"; + Map param = new HashMap<>(); + param.put("serviceName", "app-test"); + CallbackMap callbackMap = new CallbackMap<>(); + nacosRestTemplate.get(url, Header.newInstance(), + Query.newInstance().initParams(param), Map.class, callbackMap); + Thread.sleep(2000); + HttpRestResult restResult = callbackMap.getRestResult(); + System.out.println(restResult.getData()); + System.out.println(restResult.getHeader()); + Assert.assertTrue(restResult.ok()); + Assert.assertEquals(restResult.getData().get("name"), "DEFAULT_GROUP@@app-test"); + } + + @Test + public void test_url_delete() throws Exception { + String url = NACOS_SERVER_URL + "/instance"; + Query query = Query.newInstance().addParam("ip", "11.11.11.11") + .addParam("port", "8080").addParam("serviceName", "app-test"); + CallbackMap callbackMap = new CallbackMap<>(); + nacosRestTemplate.delete(url, Header.newInstance(), query, String.class, + callbackMap); + Thread.sleep(2000); + HttpRestResult restResult = callbackMap.getRestResult(); + System.out.println(restResult.getData()); + System.out.println(restResult.getHeader()); + Assert.assertTrue(restResult.ok()); + } + + private class CallbackMap implements Callback { + + private HttpRestResult restResult; + + private Throwable throwable; + + @Override + public void onReceive(RestResult result) { + restResult = (HttpRestResult) result; + } + + @Override + public void onError(Throwable throwable) { + this.throwable = throwable; + } + + @Override + public void onCancel() { + + } + + public HttpRestResult getRestResult() { + return restResult; + } + + public Throwable getThrowable() { + return throwable; + } + } + +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/core/HttpClient4Test.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/core/HttpClient4Test.java new file mode 100644 index 0000000000..84aff5d9d1 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/nacos/core/HttpClient4Test.java @@ -0,0 +1,49 @@ +package com.alibaba.cloud.integration.test.nacos.core; + +import java.net.URL; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +public class HttpClient4Test { + + protected URL baseURL; + + @Autowired + protected TestRestTemplate restTemplate; + + protected ResponseEntity request(String path, + MultiValueMap params, Class clazz) { + + HttpHeaders headers = new HttpHeaders(); + + HttpEntity entity = new HttpEntity(headers); + + UriComponentsBuilder builder = UriComponentsBuilder + .fromHttpUrl(this.baseURL.toString() + path).queryParams(params); + + return this.restTemplate.exchange(builder.toUriString(), HttpMethod.GET, entity, + clazz); + } + + protected ResponseEntity request(String path, + MultiValueMap params, Class clazz, HttpMethod httpMethod) { + + HttpHeaders headers = new HttpHeaders(); + + HttpEntity entity = new HttpEntity(headers); + + UriComponentsBuilder builder = UriComponentsBuilder + .fromHttpUrl(this.baseURL.toString() + path).queryParams(params); + + return this.restTemplate.exchange(builder.toUriString(), httpMethod, entity, + clazz); + } + +} \ No newline at end of file diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/rocketmq/RocketmqContainer.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/rocketmq/RocketmqContainer.java new file mode 100644 index 0000000000..e654d1452d --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/rocketmq/RocketmqContainer.java @@ -0,0 +1,162 @@ +package com.alibaba.cloud.integration.test.rocketmq; + +import java.time.Duration; +import java.util.Objects; +import java.util.UUID; + +import com.alibaba.cloud.integration.common.ChaosContainer; +import com.alibaba.cloud.integration.utils.DockerUtils; +import lombok.extern.slf4j.Slf4j; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; + +import static java.time.temporal.ChronoUnit.SECONDS; + +@Slf4j +public abstract class RocketmqContainer> + extends ChaosContainer { + + public static final int INVALID_PORT = -1; + public static final int ZK_PORT = 2181; + public static final int CS_PORT = 2184; + public static final int BOOKIE_PORT = 3181; + public static final int BROKER_PORT = 6650; + public static final int BROKER_HTTP_PORT = 8080; + + public static final String DEFAULT_IMAGE_NAME = System.getenv() + .getOrDefault("TEST_IMAGE_NAME", ""); + + /** + * For debugging purposes, it is useful to have the ability to leave containers + * running. This mode can be activated by setting environment variables + * CONTAINERS_LEAVE_RUNNING=true and TESTCONTAINERS_REUSE_ENABLE=true After debugging, + * one can use this command to kill all containers that were left running: docker kill + * $(docker ps -q --filter "label=markcontainer=true") + */ + public static final boolean CONTAINERS_LEAVE_RUNNING = Boolean + .parseBoolean(System.getenv("CONTAINERS_LEAVE_RUNNING")); + + private final String hostname; + private final String serviceName; + private final String serviceEntryPoint; + private final int servicePort; + private final int httpPort; + private final String httpPath; + + public RocketmqContainer(String clusterName, String hostname, String serviceName, + String serviceEntryPoint, int servicePort, int httpPort) { + this(clusterName, hostname, serviceName, serviceEntryPoint, servicePort, httpPort, + "/metrics"); + } + + public RocketmqContainer(String clusterName, String hostname, String serviceName, + String serviceEntryPoint, int servicePort, int httpPort, String httpPath) { + this(clusterName, hostname, serviceName, serviceEntryPoint, servicePort, httpPort, + httpPath, DEFAULT_IMAGE_NAME); + } + + public RocketmqContainer(String clusterName, String hostname, String serviceName, + String serviceEntryPoint, int servicePort, int httpPort, String httpPath, + String pulsarImageName) { + super(clusterName, pulsarImageName); + this.hostname = hostname; + this.serviceName = serviceName; + this.serviceEntryPoint = serviceEntryPoint; + this.servicePort = servicePort; + this.httpPort = httpPort; + this.httpPath = httpPath; + + configureLeaveContainerRunning(this); + } + + public static void configureLeaveContainerRunning(GenericContainer container) { + if (CONTAINERS_LEAVE_RUNNING) { + // use Testcontainers reuse containers feature to leave the container running + container.withReuse(true); + // add label that can be used to find containers that are left running. + container.withLabel("markcontainer", "true"); + // add a random label to prevent reuse of containers + container.withLabel("markcontainer.random", UUID.randomUUID().toString()); + } + } + + @Override + protected void beforeStop() { + super.beforeStop(); + if (null != getContainerId()) { + DockerUtils.dumpContainerDirToTargetCompressed(getDockerClient(), + getContainerId(), "/var/log/pulsar"); + } + } + + @Override + public void stop() { + if (CONTAINERS_LEAVE_RUNNING) { + log.warn("Ignoring stop due to CONTAINERS_LEAVE_RUNNING=true."); + return; + } + super.stop(); + } + + @Override + public String getContainerName() { + return clusterName + "-" + hostname; + } + + @Override + protected void configure() { + super.configure(); + if (httpPort > 0) { + addExposedPorts(httpPort); + } + if (servicePort > 0) { + addExposedPort(servicePort); + } + } + + protected void beforeStart() { + } + + protected void afterStart() { + } + + @Override + public void start() { + if (httpPort > 0 && servicePort < 0) { + this.waitStrategy = new HttpWaitStrategy().forPort(httpPort) + .forStatusCode(200).forPath(httpPath) + .withStartupTimeout(Duration.of(300, SECONDS)); + } + else if (httpPort > 0 || servicePort > 0) { + this.waitStrategy = new HostPortWaitStrategy() + .withStartupTimeout(Duration.of(300, SECONDS)); + } + this.withCreateContainerCmdModifier(createContainerCmd -> { + createContainerCmd.withHostName(hostname); + createContainerCmd.withName(getContainerName()); + createContainerCmd.withEntrypoint(serviceEntryPoint); + }); + + beforeStart(); + super.start(); + afterStart(); + log.info("[{}] Start pulsar service {} at container {}", getContainerName(), + serviceName, getContainerId()); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RocketmqContainer)) { + return false; + } + + RocketmqContainer another = (RocketmqContainer) o; + return getContainerId().equals(another.getContainerId()) && super.equals(another); + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + Objects.hash(getContainerId()); + } +} diff --git a/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/rocketmq/RocketmqDebeziumSourceTests.java b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/rocketmq/RocketmqDebeziumSourceTests.java new file mode 100644 index 0000000000..4fe8422d06 --- /dev/null +++ b/spring-cloud-alibaba-tests/spring-cloud-alibaba-testcontainers/src/test/java/com/alibaba/cloud/integration/test/rocketmq/RocketmqDebeziumSourceTests.java @@ -0,0 +1,4 @@ +package com.alibaba.cloud.integration.test.rocketmq; + +public class RocketmqDebeziumSourceTests { +}