From f389760310bf9353197a4f010bc2720ac3ded9bb Mon Sep 17 00:00:00 2001 From: click2cloud-rahul Date: Tue, 19 May 2020 19:27:54 +0530 Subject: [PATCH 1/5] Added junit test framework with gradle build. --- testhelper/open-sds-junit/.gitignore | 12 + testhelper/open-sds-junit/build.gradle | 28 ++ .../open-sds-junit/environment_variable.txt | 4 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58695 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + testhelper/open-sds-junit/gradlew | 183 ++++++++++++ testhelper/open-sds-junit/gradlew.bat | 100 +++++++ testhelper/open-sds-junit/settings.gradle | 2 + .../main/java/com/opensds/HttpHandler.java | 261 ++++++++++++++++++ .../jsonmodels/akskresponses/AKSKHolder.java | 42 +++ .../jsonmodels/akskresponses/Blob.java | 37 +++ .../jsonmodels/akskresponses/Credentials.java | 94 +++++++ .../jsonmodels/akskresponses/Links.java | 22 ++ .../akskresponses/SignatureKey.java | 70 +++++ .../jsonmodels/authtokensrequests/Auth.java | 38 +++ .../authtokensrequests/AuthHolder.java | 26 ++ .../jsonmodels/authtokensrequests/Domain.java | 26 ++ .../authtokensrequests/Identity.java | 42 +++ .../authtokensrequests/Project.java | 38 +++ .../jsonmodels/authtokensrequests/Scope.java | 26 ++ .../jsonmodels/authtokensrequests/Token.java | 30 ++ .../authtokensresponses/AuthTokenHolder.java | 37 +++ .../authtokensresponses/Catalog.java | 65 +++++ .../authtokensresponses/Domain.java | 38 +++ .../authtokensresponses/Domain_.java | 38 +++ .../authtokensresponses/Endpoint.java | 74 +++++ .../authtokensresponses/Project.java | 50 ++++ .../jsonmodels/authtokensresponses/Role.java | 38 +++ .../jsonmodels/authtokensresponses/Token.java | 124 +++++++++ .../jsonmodels/authtokensresponses/User.java | 62 +++++ .../addbackend/AddBackendInputHolder.java | 68 +++++ .../createbucket/CreateBucketFileInput.java | 37 +++ .../createbucket/uploadobjectinputfile.java | 19 ++ .../jsonmodels/logintokensrequests/Auth.java | 25 ++ .../logintokensrequests/AuthHolder.java | 26 ++ .../logintokensrequests/Domain.java | 25 ++ .../logintokensrequests/Identity.java | 38 +++ .../logintokensrequests/Password.java | 25 ++ .../jsonmodels/logintokensrequests/User.java | 47 ++++ .../jsonmodels/tokensresponses/Domain.java | 38 +++ .../jsonmodels/tokensresponses/Token.java | 75 +++++ .../tokensresponses/TokenHolder.java | 37 +++ .../jsonmodels/tokensresponses/User.java | 62 +++++ .../jsonmodels/typesresponse/Type.java | 38 +++ .../jsonmodels/typesresponse/TypesHolder.java | 40 +++ .../java/com/opensds/utils/BinaryUtils.java | 68 +++++ .../main/java/com/opensds/utils/Constant.java | 6 + .../java/com/opensds/utils/ConstantUrl.java | 93 +++++++ .../java/com/opensds/utils/HeadersName.java | 12 + .../main/java/com/opensds/utils/Logger.java | 41 +++ .../java/com/opensds/utils/TextUtils.java | 12 + .../main/java/com/opensds/utils/Utils.java | 101 +++++++ .../opensds/utils/signature/SodaV4Signer.java | 41 +++ .../inputs/createbucket/bucket_b13876524.json | 5 + .../inputs/createbucket/ibm-cos_b1321.json | 9 + .../test/java/CreateBucketBackendTest.java | 123 +++++++++ 56 files changed, 2723 insertions(+) create mode 100644 testhelper/open-sds-junit/.gitignore create mode 100644 testhelper/open-sds-junit/build.gradle create mode 100644 testhelper/open-sds-junit/environment_variable.txt create mode 100644 testhelper/open-sds-junit/gradle/wrapper/gradle-wrapper.jar create mode 100644 testhelper/open-sds-junit/gradle/wrapper/gradle-wrapper.properties create mode 100644 testhelper/open-sds-junit/gradlew create mode 100644 testhelper/open-sds-junit/gradlew.bat create mode 100644 testhelper/open-sds-junit/settings.gradle create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/AKSKHolder.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Blob.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Credentials.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Links.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/SignatureKey.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Auth.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/AuthHolder.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Domain.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Identity.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Project.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Scope.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Token.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/AuthTokenHolder.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Catalog.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Domain.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Domain_.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Endpoint.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Project.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Role.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Token.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/User.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/AddBackendInputHolder.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createbucket/CreateBucketFileInput.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createbucket/uploadobjectinputfile.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Auth.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/AuthHolder.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Domain.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Identity.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Password.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/User.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/Domain.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/Token.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/TokenHolder.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/User.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/typesresponse/Type.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/typesresponse/TypesHolder.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/BinaryUtils.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/HeadersName.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/Logger.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/TextUtils.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/Utils.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/signature/SodaV4Signer.java create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/createbucket/bucket_b13876524.json create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/createbucket/ibm-cos_b1321.json create mode 100644 testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java diff --git a/testhelper/open-sds-junit/.gitignore b/testhelper/open-sds-junit/.gitignore new file mode 100644 index 000000000..1f6c27656 --- /dev/null +++ b/testhelper/open-sds-junit/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +*.idea +.DS_Store +/build +/captures +**/out/ +**/gen/ +**/bin/ +**/build/ +/environment_variable diff --git a/testhelper/open-sds-junit/build.gradle b/testhelper/open-sds-junit/build.gradle new file mode 100644 index 000000000..f38eaf9b0 --- /dev/null +++ b/testhelper/open-sds-junit/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'java' +} + +group 'www.opensds.io' +version '1.0' + +repositories { + mavenCentral() +} + +sourceCompatibility = 1.8 + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testImplementation('junit:junit:4.13') + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' + + implementation 'com.google.code.gson:gson:2.8.6' + implementation("com.squareup.okhttp3:okhttp:4.6.0") + compile group: 'org.json', name: 'json', version: '20090211' + implementation("com.google.guava:guava:29.0-jre") + +// implementation platform('com.amazonaws:aws-java-sdk-bom:1.11.475') +// implementation 'com.amazonaws:aws-java-sdk-s3' + compile group: 'uk.co.lucasweb', name: 'aws-v4-signer-java', version: '1.2' +} diff --git a/testhelper/open-sds-junit/environment_variable.txt b/testhelper/open-sds-junit/environment_variable.txt new file mode 100644 index 000000000..3fed77c02 --- /dev/null +++ b/testhelper/open-sds-junit/environment_variable.txt @@ -0,0 +1,4 @@ +// Set your provider details here +src\main\resources\inputs\createbucket +// Before test execute setup this variable in environment variable +HOST_IP=***.***.*.***;INPUT_PATH=D:/SODA-TestProject/OpenSdsGradle;PORT_TENANT_ID=:****;PORT=:**** \ No newline at end of file diff --git a/testhelper/open-sds-junit/gradle/wrapper/gradle-wrapper.jar b/testhelper/open-sds-junit/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f3d88b1c2faf2fc91d853cd5d4242b5547257070 GIT binary patch literal 58695 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 literal 0 HcmV?d00001 diff --git a/testhelper/open-sds-junit/gradle/wrapper/gradle-wrapper.properties b/testhelper/open-sds-junit/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ba94df845 --- /dev/null +++ b/testhelper/open-sds-junit/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/testhelper/open-sds-junit/gradlew b/testhelper/open-sds-junit/gradlew new file mode 100644 index 000000000..2fe81a7d9 --- /dev/null +++ b/testhelper/open-sds-junit/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/testhelper/open-sds-junit/gradlew.bat b/testhelper/open-sds-junit/gradlew.bat new file mode 100644 index 000000000..9618d8d96 --- /dev/null +++ b/testhelper/open-sds-junit/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/testhelper/open-sds-junit/settings.gradle b/testhelper/open-sds-junit/settings.gradle new file mode 100644 index 000000000..8aec38404 --- /dev/null +++ b/testhelper/open-sds-junit/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'open-sds-junit' + diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java b/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java new file mode 100644 index 000000000..a155c66b6 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java @@ -0,0 +1,261 @@ +package com.opensds; + +import com.google.gson.Gson; +import com.opensds.jsonmodels.akskresponses.AKSKHolder; +import com.opensds.jsonmodels.akskresponses.SignatureKey; +import com.opensds.jsonmodels.authtokensrequests.Project; +import com.opensds.jsonmodels.authtokensrequests.Scope; +import com.opensds.jsonmodels.authtokensresponses.AuthTokenHolder; +import com.opensds.jsonmodels.inputs.addbackend.AddBackendInputHolder; +import com.opensds.jsonmodels.inputs.createbucket.CreateBucketFileInput; +import com.opensds.jsonmodels.logintokensrequests.*; +import com.opensds.jsonmodels.tokensresponses.TokenHolder; +import com.opensds.jsonmodels.typesresponse.TypesHolder; +import com.opensds.utils.*; +import com.opensds.utils.signature.SodaV4Signer; +import okhttp3.*; +import uk.co.lucasweb.aws.v4.signer.Header; + +public class HttpHandler { + private OkHttpClient client = new OkHttpClient(); + + public SignatureKey getAkSkList(String x_auth_token, String userId) { + SignatureKey signatureKey = new SignatureKey(); + String url = ConstantUrl.getInstance().getAksList(userId); + Logger.logString("URL: " + url); + try { + Gson gson = new Gson(); + Request request = new Request.Builder() + .url(url) + .get() + .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) + .addHeader(HeadersName.X_AUTH_TOKEN, x_auth_token) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + Response response = client.newCall(request).execute(); + String responseBody = response.body().string(); + Logger.logString("Response: " + responseBody); + AKSKHolder akskHolder = gson.fromJson(responseBody, AKSKHolder.class); + Logger.logObject(akskHolder); + // build the SignatureKey struct and set the values + new Runnable() { + @Override + public void run() { + signatureKey.setSecretAccessKey(akskHolder.getCredentials()[0].getBlobObj().getSecret()); + signatureKey.setAccessKey(akskHolder.getCredentials()[0].getBlobObj().getAccess()); + String regionName = "us-east-1"; + signatureKey.setRegionName(regionName); + String serviceName = "s3"; + signatureKey.setServiceName(serviceName); + } + }.run(); + } catch (Exception e) { + e.printStackTrace(); + } + return signatureKey; + } + + public TokenHolder loginAndGetToken() { + TokenHolder tokenHolder = null; + try { + Auth auth = new Auth(); + auth.setIdentity(new Identity()); + auth.getIdentity().getMethods().add("password"); + + auth.getIdentity().setPassword(new Password()); + auth.getIdentity().getPassword().setUser(new User()); + auth.getIdentity().getPassword().getUser().setName("admin"); + auth.getIdentity().getPassword().getUser().setPassword("opensds@123"); + + auth.getIdentity().getPassword().getUser().setDomain(new Domain()); + auth.getIdentity().getPassword().getUser().getDomain().setName("Default"); + + AuthHolder authHolder = new AuthHolder(); + authHolder.setAuth(auth); + + Gson gson = new Gson(); + RequestBody body = RequestBody.create(gson.toJson(authHolder), + MediaType.parse("application/json; charset=utf-8")); + String url = ConstantUrl.getInstance().getTokenLogin(); + Logger.logString("URL: " + url); + Request request = new Request.Builder() + .url(url) + .post(body) + .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + Response response = client.newCall(request).execute(); + String responseBody = response.body().string(); + Logger.logString("Response code: " + response.code()); + Logger.logString("Response body: " + responseBody); + tokenHolder = gson.fromJson(responseBody, TokenHolder.class); + tokenHolder.setResponseHeaderSubjectToken(response.header("X-Subject-Token")); + } catch (Exception e) { + e.printStackTrace(); + } + return tokenHolder; + } + + public AuthTokenHolder getAuthToken(String x_auth_token) { + AuthTokenHolder tokenHolder = null; + try { + com.opensds.jsonmodels.authtokensrequests.Auth auth = new com.opensds.jsonmodels.authtokensrequests.Auth(); + auth.setIdentity(new com.opensds.jsonmodels.authtokensrequests.Identity()); + auth.getIdentity().getMethods().add("token"); + auth.getIdentity().setToken(new com.opensds.jsonmodels.authtokensrequests.Token(x_auth_token)); + + auth.setScope(new Scope()); + auth.getScope().setProject(new Project()); + auth.getScope().getProject().setName("admin"); + auth.getScope().getProject().setDomain(new com.opensds.jsonmodels.authtokensrequests.Domain()); + auth.getScope().getProject().getDomain().setId("default"); + com.opensds.jsonmodels.authtokensrequests.AuthHolder authHolder = new com.opensds.jsonmodels + .authtokensrequests.AuthHolder(); + authHolder.setAuth(auth); + + Gson gson = new Gson(); + RequestBody body = RequestBody.create(gson.toJson(authHolder), + MediaType.parse("application/json; charset=utf-8")); + String url = ConstantUrl.getInstance().getTokenLogin(); + Logger.logString("URL: " + url); + Request request = new Request.Builder() + .url(url) + .post(body) + .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + Response response = client.newCall(request).execute(); + String responseBody = response.body().string(); + Logger.logString("Response code: " + response.code()); + Logger.logString("Response body: " + responseBody); + tokenHolder = new com.opensds.jsonmodels.authtokensresponses.AuthTokenHolder(); + tokenHolder = gson.fromJson(responseBody, com.opensds.jsonmodels.authtokensresponses.AuthTokenHolder.class); + tokenHolder.setResponseHeaderSubjectToken(response.header("X-Subject-Token")); + } catch (Exception e) { + e.printStackTrace(); + } + return tokenHolder; + } + + public TypesHolder getTypes(String x_auth_token, String projId) { + TypesHolder typesHolder = null; + try { + Gson gson = new Gson(); + String url = ConstantUrl.getInstance().getTypesUrl(projId); + Logger.logString("URL: " + url); + Request request = new Request.Builder() + .url(url) + .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) + .addHeader(HeadersName.X_AUTH_TOKEN, x_auth_token) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + Response response = client.newCall(request).execute(); + String responseBody = response.body().string(); + Logger.logString("Response code: " + response.code()); + Logger.logString("Response body: " + responseBody); + typesHolder = gson.fromJson(responseBody, TypesHolder.class); + } catch (Exception e) { + e.printStackTrace(); + } + return typesHolder; + } + + public int addBackend(String x_auth_token, String projId, AddBackendInputHolder inputHolder) { + int code = -1; + try { + Gson gson = new Gson(); + RequestBody body = RequestBody.create(gson.toJson(inputHolder), + MediaType.parse("application/json; charset=utf-8")); + String url = ConstantUrl.getInstance().getAddBackendUrl(projId); + Logger.logString("URL: " + url); + Request request = new Request.Builder() + .url(url) + .post(body) + .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) + .addHeader(HeadersName.X_AUTH_TOKEN, x_auth_token) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + Response response = client.newCall(request).execute(); + code = response.code(); + Logger.logString("Response code: " + code); + Logger.logString("Response body: " + response.body().string()); + } catch (Exception e) { + e.printStackTrace(); + } + return code; + } + + public int createBucket(CreateBucketFileInput input, String bucketName, SignatureKey signatureKey) { + int code = -1; + try { + RequestBody body = RequestBody.create(input.getXmlPayload(), + MediaType.parse("application/xml")); + String payload = BinaryUtils.toHex(BinaryUtils.hash(input.getXmlPayload())); + String date = Utils.getDate(); + String url = ConstantUrl.getInstance().getCreateBucketUrl(bucketName); + Logger.logString("URL: " + url); + String host = Utils.getHost(url); + Header[] headerList = new Header[]{new Header(HeadersName.HOST, host), + new Header(HeadersName.X_AMZ_DATE, date), + new Header(HeadersName.X_AMZ_CONTENT_SHA256, payload)}; + String authorization = SodaV4Signer.getSignature("PUT", url, signatureKey.getAccessKey(), + signatureKey.getSecretAccessKey(), payload, signatureKey.getRegionName(), headerList); + Logger.logString("Authorization: " + authorization); + + Request request = new Request.Builder() + .url(url) + .put(body) + .header(HeadersName.HOST, host) + .header(HeadersName.AUTHORIZATION, authorization) + .header(HeadersName.X_AMZ_CONTENT_SHA256, payload) + .header(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_XML) + .header(HeadersName.X_AMZ_DATE, date) + .build(); + + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + Response response = client.newCall(request).execute(); + code = response.code(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + response.body().string()); + } catch (Exception e) { + e.printStackTrace(); + } + return code; + } + + public Response getBuckets(SignatureKey signatureKey) { + Response response = null; + String url = ConstantUrl.getInstance().getListBucketUrl(); + String payload = BinaryUtils.toHex(BinaryUtils.hash("")); + String date = Utils.getDate(); + Logger.logString("URL: " + url); + String host = Utils.getHost(url); + Header[] headerList = new Header[]{new Header(HeadersName.HOST, host), + new Header(HeadersName.X_AMZ_DATE, date), + new Header(HeadersName.X_AMZ_CONTENT_SHA256, payload)}; + String authorization = SodaV4Signer.getSignature("GET", url, signatureKey.getAccessKey(), + signatureKey.getSecretAccessKey(), payload, signatureKey.getRegionName(), headerList); + Logger.logString("Authorization: " + authorization); + try { + Request request = new Request.Builder() + .url(url) + .get() + .addHeader(HeadersName.AUTHORIZATION, authorization) + .addHeader(HeadersName.X_AMZ_CONTENT_SHA256, payload) + .addHeader(HeadersName.X_AMZ_DATE, date) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + response = client.newCall(request).execute(); + } catch (Exception e) { + e.printStackTrace(); + } + return response; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/AKSKHolder.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/AKSKHolder.java new file mode 100644 index 000000000..e9e44c36d --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/AKSKHolder.java @@ -0,0 +1,42 @@ +package com.opensds.jsonmodels.akskresponses; + +import com.google.gson.Gson; + +public class AKSKHolder { + + private Credentials[] credentials; + + private Links links; + + public Credentials[] getCredentials() { + return credentials; + } + + public void setCredentials(Credentials[] credentials) { + this.credentials = credentials; + } + + public Links getLinks() { + return links; + } + + public void setLinks(Links links) { + this.links = links; + } + + @Override + public String toString() { + Gson gson = new Gson(); + StringBuffer sb = new StringBuffer(); + sb.append("AKSKHolder [credentials = "); + for (Credentials c : credentials) { + c.blobObj = gson.fromJson(c.getblob(), Blob.class); + } + for (Credentials c : credentials) { + sb.append("\n\t"); + sb.append(c); + } + sb.append(", \nlinks = " + links + "]"); + return sb.toString(); + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Blob.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Blob.java new file mode 100644 index 000000000..345074ecc --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Blob.java @@ -0,0 +1,37 @@ +package com.opensds.jsonmodels.akskresponses; + +public class Blob { + + + private String access; + + private String secret; + + + public String getAccess() { + return access; + } + + + public void setAccess(String access) { + this.access = access; + } + + + public String getSecret() { + return secret; + } + + + public void setSecret(String secret) { + this.secret = secret; + } + + @Override + public String toString() { + return "Blob{" + + "access='" + access + '\'' + + ", secret='" + secret + '\'' + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Credentials.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Credentials.java new file mode 100644 index 000000000..335103f5b --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Credentials.java @@ -0,0 +1,94 @@ +package com.opensds.jsonmodels.akskresponses; + +import com.google.gson.Gson; + +public class Credentials +{ + private String blob; + public Blob blobObj; + + private String user_id; + + private String project_id; + + private Links links; + + private String id; + + private String type; + + public String getblob () + { + return blob; + } + + public void setblob (String blob) + { + this.blob = blob; + Gson gson = new Gson(); + this.blobObj = gson.fromJson(this.blob, Blob.class); + System.out.println(this.blobObj); + } + + public String getUser_id () + { + return user_id; + } + + public void setUser_id (String user_id) + { + this.user_id = user_id; + } + + public String getProject_id () + { + return project_id; + } + + public void setProject_id (String project_id) + { + this.project_id = project_id; + } + + public Links getLinks () + { + return links; + } + + public void setLinks (Links links) + { + this.links = links; + } + + public String getId () + { + return id; + } + + public void setId (String id) + { + this.id = id; + } + + public String getType () + { + return type; + } + + public void setType (String type) + { + this.type = type; + } + + @Override + public String toString() + { + //System.out.println(blobObj); + return "Credential [blob = "+blobObj+", user_id = "+user_id+", project_id = "+project_id+", links = "+links+", id = "+id+", type = "+type+"]"; + } + + public Blob getBlobObj () + { + return blobObj; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Links.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Links.java new file mode 100644 index 000000000..01aa3c618 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/Links.java @@ -0,0 +1,22 @@ +package com.opensds.jsonmodels.akskresponses; + +public class Links +{ + private String self; + + public String getSelf () + { + return self; + } + + public void setSelf (String self) + { + this.self = self; + } + + @Override + public String toString() + { + return "Links [self = "+self+"]"; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/SignatureKey.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/SignatureKey.java new file mode 100644 index 000000000..73a8b1931 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/akskresponses/SignatureKey.java @@ -0,0 +1,70 @@ +package com.opensds.jsonmodels.akskresponses; + +public class SignatureKey { + String secretAccessKey; + String dateStamp; + String dayDate; + String regionName; + String serviceName; + String AccessKey; + + public String getSecretAccessKey() { + return secretAccessKey; + } + + public void setSecretAccessKey(String secretAccessKey) { + this.secretAccessKey = secretAccessKey; + } + + public String getDateStamp() { + return dateStamp; + } + + public void setDateStamp(String dateStamp) { + this.dateStamp = dateStamp; + } + + public String getDayDate() { + return dayDate; + } + + public void setDayDate(String dayDate) { + this.dayDate = dayDate; + } + + public String getRegionName() { + return regionName; + } + + public void setRegionName(String regionName) { + this.regionName = regionName; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getAccessKey() { + return AccessKey; + } + + public void setAccessKey(String accessKey) { + AccessKey = accessKey; + } + + @Override + public String toString() { + return "SignatureKey{" + + "secretAccessKey='" + secretAccessKey + '\'' + + ", dateStamp='" + dateStamp + '\'' + + ", dayDate='" + dayDate + '\'' + + ", regionName='" + regionName + '\'' + + ", serviceName='" + serviceName + '\'' + + ", AccessKey='" + AccessKey + '\'' + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Auth.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Auth.java new file mode 100644 index 000000000..4acbeb80b --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Auth.java @@ -0,0 +1,38 @@ +package com.opensds.jsonmodels.authtokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Auth { + + @SerializedName("identity") + @Expose + private Identity identity; + @SerializedName("scope") + @Expose + private Scope scope; + + public Identity getIdentity() { + return identity; + } + + public void setIdentity(Identity identity) { + this.identity = identity; + } + + public Scope getScope() { + return scope; + } + + public void setScope(Scope scope) { + this.scope = scope; + } + + @Override + public String toString() { + return "Auth{" + + "identity=" + identity + + ", scope=" + scope + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/AuthHolder.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/AuthHolder.java new file mode 100644 index 000000000..ccb980bb9 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/AuthHolder.java @@ -0,0 +1,26 @@ +package com.opensds.jsonmodels.authtokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class AuthHolder { + + @SerializedName("auth") + @Expose + private Auth auth; + + public Auth getAuth() { + return auth; + } + + public void setAuth(Auth auth) { + this.auth = auth; + } + + @Override + public String toString() { + return "AuthHolder{" + + "auth=" + auth + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Domain.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Domain.java new file mode 100644 index 000000000..4193a4330 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Domain.java @@ -0,0 +1,26 @@ +package com.opensds.jsonmodels.authtokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Domain { + + @SerializedName("id") + @Expose + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "Domain{" + + "id='" + id + '\'' + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Identity.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Identity.java new file mode 100644 index 000000000..df8063179 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Identity.java @@ -0,0 +1,42 @@ +package com.opensds.jsonmodels.authtokensrequests; + + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class Identity { + + @SerializedName("methods") + @Expose + private List methods = new ArrayList<>(); + @SerializedName("token") + @Expose + private Token token; + + public List getMethods() { + return methods; + } + + public void setMethods(List methods) { + this.methods = methods; + } + + public Token getToken() { + return token; + } + + public void setToken(Token token) { + this.token = token; + } + + @Override + public String toString() { + return "Identity{" + + "methods=" + methods + + ", token=" + token + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Project.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Project.java new file mode 100644 index 000000000..006884b93 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Project.java @@ -0,0 +1,38 @@ +package com.opensds.jsonmodels.authtokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Project { + + @SerializedName("name") + @Expose + private String name; + @SerializedName("domain") + @Expose + private Domain domain; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Domain getDomain() { + return domain; + } + + public void setDomain(Domain domain) { + this.domain = domain; + } + + @Override + public String toString() { + return "Project{" + + "name='" + name + '\'' + + ", domain=" + domain + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Scope.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Scope.java new file mode 100644 index 000000000..044993002 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Scope.java @@ -0,0 +1,26 @@ +package com.opensds.jsonmodels.authtokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Scope { + + @SerializedName("project") + @Expose + private Project project; + + public Project getProject() { + return project; + } + + public void setProject(Project project) { + this.project = project; + } + + @Override + public String toString() { + return "Scope{" + + "project=" + project + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Token.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Token.java new file mode 100644 index 000000000..78b76fb58 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensrequests/Token.java @@ -0,0 +1,30 @@ +package com.opensds.jsonmodels.authtokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Token { + + @SerializedName("id") + @Expose + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "Token{" + + "id='" + id + '\'' + + '}'; + } + + public Token(String id) { + this.id = id; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/AuthTokenHolder.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/AuthTokenHolder.java new file mode 100644 index 000000000..fc9cca6d3 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/AuthTokenHolder.java @@ -0,0 +1,37 @@ +package com.opensds.jsonmodels.authtokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class AuthTokenHolder { + + String responseHeaderSubjectToken; + + @SerializedName("token") + @Expose + private Token token; + + public Token getToken() { + return token; + } + + public void setToken(Token token) { + this.token = token; + } + + @Override + public String toString() { + return "AuthTokenHolder{" + + "responseHeaderSubjectToken='" + responseHeaderSubjectToken + '\'' + + ", token=" + token + + '}'; + } + + public String getResponseHeaderSubjectToken() { + return responseHeaderSubjectToken; + } + + public void setResponseHeaderSubjectToken(String responseHeaderSubjectToken) { + this.responseHeaderSubjectToken = responseHeaderSubjectToken; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Catalog.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Catalog.java new file mode 100644 index 000000000..45ffb8e33 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Catalog.java @@ -0,0 +1,65 @@ +package com.opensds.jsonmodels.authtokensresponses; + + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class Catalog { + + @SerializedName("endpoints") + @Expose + private List endpoints = null; + @SerializedName("type") + @Expose + private String type; + @SerializedName("id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + + public List getEndpoints() { + return endpoints; + } + + public void setEndpoints(List endpoints) { + this.endpoints = endpoints; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Catalog{" + + "endpoints=" + endpoints + + ", type='" + type + '\'' + + ", id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Domain.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Domain.java new file mode 100644 index 000000000..32ab1e093 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Domain.java @@ -0,0 +1,38 @@ +package com.opensds.jsonmodels.authtokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Domain { + + @SerializedName("id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Domain{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Domain_.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Domain_.java new file mode 100644 index 000000000..8503ea864 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Domain_.java @@ -0,0 +1,38 @@ +package com.opensds.jsonmodels.authtokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Domain_ { + + @SerializedName("id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Domain_{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Endpoint.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Endpoint.java new file mode 100644 index 000000000..9425e1878 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Endpoint.java @@ -0,0 +1,74 @@ +package com.opensds.jsonmodels.authtokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Endpoint { + + @SerializedName("region_id") + @Expose + private String regionId; + @SerializedName("url") + @Expose + private String url; + @SerializedName("region") + @Expose + private String region; + @SerializedName("interface") + @Expose + private String _interface; + @SerializedName("id") + @Expose + private String id; + + public String getRegionId() { + return regionId; + } + + public void setRegionId(String regionId) { + this.regionId = regionId; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getInterface() { + return _interface; + } + + public void setInterface(String _interface) { + this._interface = _interface; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "Endpoint{" + + "regionId='" + regionId + '\'' + + ", url='" + url + '\'' + + ", region='" + region + '\'' + + ", _interface='" + _interface + '\'' + + ", id='" + id + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Project.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Project.java new file mode 100644 index 000000000..91e1e9257 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Project.java @@ -0,0 +1,50 @@ +package com.opensds.jsonmodels.authtokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Project { + + @SerializedName("domain") + @Expose + private Domain domain; + @SerializedName("id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + + public Domain getDomain() { + return domain; + } + + public void setDomain(Domain domain) { + this.domain = domain; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Project{" + + "domain=" + domain + + ", id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Role.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Role.java new file mode 100644 index 000000000..5232c377b --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Role.java @@ -0,0 +1,38 @@ +package com.opensds.jsonmodels.authtokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Role { + + @SerializedName("id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Role{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Token.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Token.java new file mode 100644 index 000000000..debe81b9d --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/Token.java @@ -0,0 +1,124 @@ +package com.opensds.jsonmodels.authtokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class Token { + + @SerializedName("is_domain") + @Expose + private Boolean isDomain; + @SerializedName("methods") + @Expose + private List methods = null; + @SerializedName("roles") + @Expose + private List roles = null; + @SerializedName("expires_at") + @Expose + private String expiresAt; + @SerializedName("project") + @Expose + private Project project; + @SerializedName("catalog") + @Expose + private List catalog = null; + @SerializedName("user") + @Expose + private User user; + @SerializedName("audit_ids") + @Expose + private List auditIds = null; + @SerializedName("issued_at") + @Expose + private String issuedAt; + + public Boolean getIsDomain() { + return isDomain; + } + + public void setIsDomain(Boolean isDomain) { + this.isDomain = isDomain; + } + + public List getMethods() { + return methods; + } + + public void setMethods(List methods) { + this.methods = methods; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public String getExpiresAt() { + return expiresAt; + } + + public void setExpiresAt(String expiresAt) { + this.expiresAt = expiresAt; + } + + public Project getProject() { + return project; + } + + public void setProject(Project project) { + this.project = project; + } + + public List getCatalog() { + return catalog; + } + + public void setCatalog(List catalog) { + this.catalog = catalog; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public List getAuditIds() { + return auditIds; + } + + public void setAuditIds(List auditIds) { + this.auditIds = auditIds; + } + + public String getIssuedAt() { + return issuedAt; + } + + public void setIssuedAt(String issuedAt) { + this.issuedAt = issuedAt; + } + + @Override + public String toString() { + return "Token{" + + "isDomain=" + isDomain + + ", methods=" + methods + + ", roles=" + roles + + ", expiresAt='" + expiresAt + '\'' + + ", project=" + project + + ", catalog=" + catalog + + ", user=" + user + + ", auditIds=" + auditIds + + ", issuedAt='" + issuedAt + '\'' + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/User.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/User.java new file mode 100644 index 000000000..70cbb6996 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/authtokensresponses/User.java @@ -0,0 +1,62 @@ +package com.opensds.jsonmodels.authtokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class User { + + @SerializedName("password_expires_at") + @Expose + private Object passwordExpiresAt; + @SerializedName("domain") + @Expose + private Domain_ domain; + @SerializedName("id") + @Expose + private String id; + @SerializedName("name") + @Expose + private String name; + + public Object getPasswordExpiresAt() { + return passwordExpiresAt; + } + + public void setPasswordExpiresAt(Object passwordExpiresAt) { + this.passwordExpiresAt = passwordExpiresAt; + } + + public Domain_ getDomain() { + return domain; + } + + public void setDomain(Domain_ domain) { + this.domain = domain; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "User{" + + "passwordExpiresAt=" + passwordExpiresAt + + ", domain=" + domain + + ", id='" + id + '\'' + + ", name='" + name + '\'' + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/AddBackendInputHolder.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/AddBackendInputHolder.java new file mode 100644 index 000000000..d850cc127 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/AddBackendInputHolder.java @@ -0,0 +1,68 @@ +package com.opensds.jsonmodels.inputs.addbackend; + +public class AddBackendInputHolder { + String name; + String type; + String region; + String endpoint; + String bucketName; + String security; + String access; + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public String getSecurity() { + return security; + } + + public void setSecurity(String security) { + this.security = security; + } + + public String getAccess() { + return access; + } + + public void setAccess(String access) { + this.access = access; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createbucket/CreateBucketFileInput.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createbucket/CreateBucketFileInput.java new file mode 100644 index 000000000..0406e28fc --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createbucket/CreateBucketFileInput.java @@ -0,0 +1,37 @@ +package com.opensds.jsonmodels.inputs.createbucket; + +public class CreateBucketFileInput { + + private String xmlPayload; + private String xmlRequestTrue; + private String xmlRequestFalse; + + public String getXmlRequestFalse() { + return xmlRequestFalse; + } + + public void setXmlRequestFalse(String xmlRequestFalse) { + this.xmlRequestFalse = xmlRequestFalse; + } + + public String getXmlRequestTrue() { + return xmlRequestTrue; + } + + public void setXmlRequestTrue(String xmlRequestTrue) { + this.xmlRequestTrue = xmlRequestTrue; + } + + public String getXmlPayload() { + return xmlPayload; + } + + public void setXmlPayload(String xmlPayload) { + this.xmlPayload = xmlPayload; + } + + @Override + public String toString() { + return "CreateBucketFileInput [xmlPayload = " + xmlPayload + "]"; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createbucket/uploadobjectinputfile.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createbucket/uploadobjectinputfile.java new file mode 100644 index 000000000..4a915089a --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createbucket/uploadobjectinputfile.java @@ -0,0 +1,19 @@ +package com.opensds.jsonmodels.inputs.createbucket; + +public class uploadobjectinputfile { + + private String xmlPayload; + + public String getXmlPayload() { + return xmlPayload; + } + + public void setXmlPayload(String xmlPayload) { + this.xmlPayload = xmlPayload; + } + + @Override + public String toString() { + return "uploadobjectinputfile [xmlPayload = " + xmlPayload + "]"; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Auth.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Auth.java new file mode 100644 index 000000000..53f7d2778 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Auth.java @@ -0,0 +1,25 @@ +package com.opensds.jsonmodels.logintokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Auth { + + @SerializedName("identity") + @Expose + private Identity identity; + + public Identity getIdentity() { + return identity; + } + + public void setIdentity(Identity identity) { + this.identity = identity; + } + + @Override + public String toString() { + return new StringBuilder().append("identity").append(identity).toString(); + } + +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/AuthHolder.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/AuthHolder.java new file mode 100644 index 000000000..5ef9d3a4e --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/AuthHolder.java @@ -0,0 +1,26 @@ +package com.opensds.jsonmodels.logintokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class AuthHolder { + + @SerializedName("auth") + @Expose + private Auth auth; + + public Auth getAuth() { + return auth; + } + + public void setAuth(Auth auth) { + this.auth = auth; + } + + @Override + public String toString() { + return "AuthHolder{" + + "auth=" + auth + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Domain.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Domain.java new file mode 100644 index 000000000..adf05c78a --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Domain.java @@ -0,0 +1,25 @@ +package com.opensds.jsonmodels.logintokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Domain { + + @SerializedName("name") + @Expose + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return new StringBuilder().append("name").append(name).toString(); + } + +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Identity.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Identity.java new file mode 100644 index 000000000..e2dde7e2c --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Identity.java @@ -0,0 +1,38 @@ +package com.opensds.jsonmodels.logintokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class Identity { + + @SerializedName("methods") + @Expose + private List methods = new ArrayList<>(); + @SerializedName("password") + @Expose + private Password password; + + public List getMethods() { + return methods; + } + + public void setMethods(List methods) { + this.methods = methods; + } + + public Password getPassword() { + return password; + } + + public void setPassword(Password password) { + this.password = password; + } + + @Override + public String toString() { + return new StringBuilder().append("methods").append(methods).append("password").append(password).toString(); + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Password.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Password.java new file mode 100644 index 000000000..6fc1d85cd --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/Password.java @@ -0,0 +1,25 @@ +package com.opensds.jsonmodels.logintokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Password { + + @SerializedName("user") + @Expose + private User user; + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + @Override + public String toString() { + return new StringBuilder().append("user").append(user).toString(); + } + +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/User.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/User.java new file mode 100644 index 000000000..38c4398c4 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/logintokensrequests/User.java @@ -0,0 +1,47 @@ +package com.opensds.jsonmodels.logintokensrequests; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class User { + + @SerializedName("name") + @Expose + private String name; + @SerializedName("domain") + @Expose + private Domain domain; + @SerializedName("password") + @Expose + private String password; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Domain getDomain() { + return domain; + } + + public void setDomain(Domain domain) { + this.domain = domain; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + return new StringBuilder().append("name").append(name).append("domain").append(domain).append("password").append(password).toString(); + } + +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/Domain.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/Domain.java new file mode 100644 index 000000000..896e7871b --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/Domain.java @@ -0,0 +1,38 @@ +package com.opensds.jsonmodels.tokensresponses; + +import com.google.gson.annotations.SerializedName; + +public class Domain { + + public @SerializedName("id") String id; + public @SerializedName("name") String name; + + public Domain(String id, String name) { + this.id = id; + this.name = name; + } + + public String getid() { + return id; + } + + public void setid(String id) { + this.id = id; + } + + public String getname() { + return name; + } + + public void setname(String name) { + this.name = name; + } + + @Override + public String toString() { + return "\n\tDomain{" + + "\n\t\tid='" + id + '\'' + + "\n\t\tname='" + name + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/Token.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/Token.java new file mode 100644 index 000000000..b47143da9 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/Token.java @@ -0,0 +1,75 @@ +package com.opensds.jsonmodels.tokensresponses; + +import java.util.List; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Token { + + @SerializedName("issued_at") + @Expose + String issuedAt; + @SerializedName("audit_ids") + @Expose + List auditIds = null; + @SerializedName("methods") + @Expose + List methods = null; + @SerializedName("expires_at") + @Expose + String expiresAt; + @SerializedName("user") + @Expose + User user; + + public String getIssuedAt() { + return issuedAt; + } + + public void setIssuedAt(String issuedAt) { + this.issuedAt = issuedAt; + } + + public List getAuditIds() { + return auditIds; + } + + public void setAuditIds(List auditIds) { + this.auditIds = auditIds; + } + + public List getMethods() { + return methods; + } + + public void setMethods(List methods) { + this.methods = methods; + } + + public String getExpiresAt() { + return expiresAt; + } + + public void setExpiresAt(String expiresAt) { + this.expiresAt = expiresAt; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + @Override + public String toString() { + return "\n\tToken{" + + "\n\t\tissuedAt='" + issuedAt + + "\n\t\tauditIds=" + auditIds + + "\n\t\tmethods=" + methods + + "\n\t\texpiresAt='" + expiresAt + + "\n\t\tuser=" + user + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/TokenHolder.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/TokenHolder.java new file mode 100644 index 000000000..1b3b061e6 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/TokenHolder.java @@ -0,0 +1,37 @@ +package com.opensds.jsonmodels.tokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class TokenHolder { + + String responseHeaderSubjectToken; + + @SerializedName("token") + @Expose + Token token; + + public Token getToken() { + return token; + } + + public void setToken(Token token) { + this.token = token; + } + + @Override + public String toString() { + return "\n\tTokenHolder{" + + "\n\t\ttoken=" + token + + "\n\treqHeaderToken=" + responseHeaderSubjectToken; + + } + + public String getResponseHeaderSubjectToken() { + return responseHeaderSubjectToken; + } + + public void setResponseHeaderSubjectToken(String responseHeaderSubjectToken) { + this.responseHeaderSubjectToken = responseHeaderSubjectToken; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/User.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/User.java new file mode 100644 index 000000000..b92c7c435 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/tokensresponses/User.java @@ -0,0 +1,62 @@ +package com.opensds.jsonmodels.tokensresponses; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class User { + + @SerializedName("password_expires_at") + @Expose + Object passwordExpiresAt; + @SerializedName("domain") + @Expose + Domain domainHolder; + @SerializedName("id") + @Expose + String id; + @SerializedName("name") + @Expose + String name; + + public Object getPasswordExpiresAt() { + return passwordExpiresAt; + } + + public void setPasswordExpiresAt(Object passwordExpiresAt) { + this.passwordExpiresAt = passwordExpiresAt; + } + + public Domain getDomainHolder() { + return domainHolder; + } + + public void setDomainHolder(Domain domainHolder) { + this.domainHolder = domainHolder; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "\n\tUser{" + + "\n\t\tpasswordExpiresAt=" + passwordExpiresAt + + "\n\t\tdomain=" + domainHolder + + "\n\t\tid='" + id + '\'' + + "\n\t\tname='" + name + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/typesresponse/Type.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/typesresponse/Type.java new file mode 100644 index 000000000..2830deef8 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/typesresponse/Type.java @@ -0,0 +1,38 @@ +package com.opensds.jsonmodels.typesresponse; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +public class Type { + + @SerializedName("name") + @Expose + private String name; + @SerializedName("description") + @Expose + private String description; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public String toString() { + return "Type{" + + "name='" + name + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/typesresponse/TypesHolder.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/typesresponse/TypesHolder.java new file mode 100644 index 000000000..32195ddeb --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/typesresponse/TypesHolder.java @@ -0,0 +1,40 @@ +package com.opensds.jsonmodels.typesresponse; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +public class TypesHolder { + + @SerializedName("types") + @Expose + private List types = null; + @SerializedName("next") + @Expose + private Integer next; + + public List getTypes() { + return types; + } + + public void setTypes(List types) { + this.types = types; + } + + public Integer getNext() { + return next; + } + + public void setNext(Integer next) { + this.next = next; + } + + @Override + public String toString() { + return "TypesHolder{" + + "types=" + types + + ", next=" + next + + '}'; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/BinaryUtils.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/BinaryUtils.java new file mode 100644 index 000000000..1fdbd4086 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/BinaryUtils.java @@ -0,0 +1,68 @@ +package com.opensds.utils; + +import java.security.MessageDigest; +import java.util.Locale; + +/** + * Utilities for encoding and decoding binary data to and from different forms. + */ +public class BinaryUtils { + + /** + * Converts byte data to a Hex-encoded string. + * + * @param data + * data to hex encode. + * + * @return hex-encoded string. + */ + public static String toHex(byte[] data) { + StringBuilder sb = new StringBuilder(data.length * 2); + for (int i = 0; i < data.length; i++) { + String hex = Integer.toHexString(data[i]); + if (hex.length() == 1) { + // Append leading zero. + sb.append("0"); + } else if (hex.length() == 8) { + // Remove ff prefix from negative numbers. + hex = hex.substring(6); + } + sb.append(hex); + } + return sb.toString().toLowerCase(Locale.getDefault()); + } + + /** + * Converts a Hex-encoded data string to the original byte data. + * + * @param hexData + * hex-encoded data to decode. + * @return decoded data from the hex string. + */ + public static byte[] fromHex(String hexData) { + byte[] result = new byte[(hexData.length() + 1) / 2]; + String hexNumber = null; + int stringOffset = 0; + int byteOffset = 0; + while (stringOffset < hexData.length()) { + hexNumber = hexData.substring(stringOffset, stringOffset + 2); + stringOffset += 2; + result[byteOffset++] = (byte) Integer.parseInt(hexNumber, 16); + } + return result; + } + + /** + * Hashes the string contents (assumed to be UTF-8) using the SHA-256 + * algorithm. + */ + public static byte[] hash(String text) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(text.getBytes("UTF-8")); + return md.digest(); + } catch (Exception e) { + throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e); + } + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java new file mode 100644 index 000000000..d12447b79 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java @@ -0,0 +1,6 @@ +package com.opensds.utils; + +public class Constant { + public static final String PATH = System.getenv("INPUT_PATH")+"/src/main/resources/inputs/"; + public static final String CREATE_BUCKET_PATH = PATH+"createbucket"; +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java new file mode 100644 index 000000000..4cd2d5fe1 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java @@ -0,0 +1,93 @@ +package com.opensds.utils; + +public class ConstantUrl { + private static ConstantUrl mConstantUrl; + private static String URL = null; + private static String PORT_TENANT_ID = null; + private static String PORT = null; + + private ConstantUrl() { + PORT_TENANT_ID = getPortTenantId(); + PORT = getPort(); + URL = getHostIp(); + } + + public static ConstantUrl getInstance() { + Logger.logString("**********************************************************************"); + if (mConstantUrl == null) { + mConstantUrl = new ConstantUrl(); + } + return mConstantUrl; + } + + /** + * Get port: This port used in there is used tenant id url + */ + public String getPortTenantId() { + return System.getenv("PORT_TENANT_ID"); + } + + /** + * Get Port: This port used in S3 services url except login or auth related url. + */ + public String getPort() { + return System.getenv("PORT"); + } + + /** + * Get Host Ip. + */ + public String getHostIp() { + return "http://" + System.getenv("HOST_IP"); + } + + /** + * Get Token Login. + */ + public String getTokenLogin() { + return URL +"/identity/v3/auth/tokens"; + } + + /** + * Get aks list. + * + * @param userId user id. + */ + public String getAksList(String userId) { + return URL +"/identity/v3/credentials?userId="+userId+"&type=ec2"; + } + + /** + * Get Types + * + * @param tenantId tenant id. + */ + public String getTypesUrl(String tenantId) { + return URL+PORT_TENANT_ID+"/"+tenantId+"/types"; + } + + /** + * Add Backend + * + * @param tenantId admin tenant id. + */ + public String getAddBackendUrl(String tenantId) { + return URL+PORT_TENANT_ID+"/"+ tenantId +"/backends"; + } + + /** + * Create Bucket + * + * @param bucketName bucket name. + */ + public String getCreateBucketUrl(String bucketName) { + return URL+PORT+"/"+ bucketName; + } + + /** + * Bucket List + */ + public String getListBucketUrl() { + return URL+PORT+"/"; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/HeadersName.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/HeadersName.java new file mode 100644 index 000000000..9677f769f --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/HeadersName.java @@ -0,0 +1,12 @@ +package com.opensds.utils; + +public class HeadersName { + public static final String AUTHORIZATION = "Authorization"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_TYPE_XML = "application/xml"; + public static final String CONTENT_TYPE_JSON = "application/json"; + public static final String X_AMZ_DATE = "X-Amz-Date"; + public static final String X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256"; + public static final String X_AUTH_TOKEN = "x-auth-token"; + public static final String HOST = "Host"; +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Logger.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Logger.java new file mode 100644 index 000000000..67f0dc038 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Logger.java @@ -0,0 +1,41 @@ +package com.opensds.utils; + +public class Logger { + + /** + * Print log in string. + * + * @param message Message + */ + public static void logString(String message) { + System.out.println(message); + } + + + /** + * Print log in int. + * + * @param message Message + */ + public static void logInt(Integer message) { + System.out.println(message); + } + + /** + * Print log in double. + * + * @param message Message + */ + public static void logDouble(Double message) { + System.out.println(message); + } + + /** + * Print log in object. + * + * @param message Message + */ + public static void logObject(Object message) { + System.out.println(message); + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/TextUtils.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/TextUtils.java new file mode 100644 index 000000000..2f11c89c0 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/TextUtils.java @@ -0,0 +1,12 @@ +package com.opensds.utils; + +public class TextUtils { + /** + * Returns true if the string is null or 0-length. + * @param str the string to be examined + * @return true if str is null or zero length + */ + public static boolean isEmpty(CharSequence str) { + return str == null || str.length() == 0; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Utils.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Utils.java new file mode 100644 index 000000000..84da66365 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Utils.java @@ -0,0 +1,101 @@ +package com.opensds.utils; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.text.SimpleDateFormat; +import java.util.*; + +public class Utils { + + /** + * format strings for the date/time and date stamps required during signing + **/ + public static final String ISO8601BasicFormat = "yyyyMMdd'T'HHmmss'Z'"; + + /** + * Get List of files + * + * @param beginPattern begin pattern (bucket) e.g: bucket_b237 + * @param path folder path e.g: resource/inputs + * @return list of files + */ + public static List listFilesMatchingBeginsWithPatternInPath(final String beginPattern, String path) { + List retFileList = new ArrayList<>(); + try { + File dir = new File(path); + assert dir.isDirectory() : "Invalid directory path: "+path; + File[] files = dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.matches("^" + beginPattern + "+[a-z_1-9-]*.json"); + } + }); + + for (File xmlfile : files) { + retFileList.add(xmlfile); + } + } catch (Exception e) { + e.printStackTrace(); + } + return retFileList; + } + + /** + * Read file content. + * + * @param file file. + * @return file content. + */ + public static String readFileContentsAsString(File file) { + String content = null; + try { + content = new String(Files.readAllBytes(file.toPath())); + } catch (IOException e) { + e.printStackTrace(); + } + return content; + } + + /** + * Get Bucket Name. + * + * @param bucketFile file path + * @return bucket name + */ + public static String getBucketName(File bucketFile){ + return bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, + bucketFile.getName().indexOf(".")); + } + + /** + * Get date. + * + * @return date. + */ + public static String getDate() { + Date now = new Date(); + SimpleDateFormat dateTimeFormat = new SimpleDateFormat(ISO8601BasicFormat); + dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC")); + return dateTimeFormat.format(now); + } + + /** + * Get Host + * + * @param url url e.g: http://192.168.34.45:6566 + * @return host e.g: 192.168.34.45:6566 + */ + public static String getHost(String url){ + String host = null; + try { + host = new URI(url).getHost()+":"+new URI(url).getPort(); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return host; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/signature/SodaV4Signer.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/signature/SodaV4Signer.java new file mode 100644 index 000000000..fe8a2741c --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/signature/SodaV4Signer.java @@ -0,0 +1,41 @@ +package com.opensds.utils.signature; + +import uk.co.lucasweb.aws.v4.signer.Header; +import uk.co.lucasweb.aws.v4.signer.HttpRequest; +import uk.co.lucasweb.aws.v4.signer.Signer; +import uk.co.lucasweb.aws.v4.signer.credentials.AwsCredentials; + +import java.net.URI; +import java.net.URISyntaxException; + +public class SodaV4Signer { + + /** + * Generate v4 Signature. + * + * @param method Method name e.g GET + * @param url URL e.g https://www.opensds.io/ + * @param accessKey Access key get from AKSK API + * @param secretKey Secret key get from AKSK API + * @param payload Request body converted in to SHA265 + * @param regionName Region name + * @param headers Headers + * @return Signature + */ + public static String getSignature(String method, String url, + String accessKey, String secretKey, + String payload, String regionName, Header... headers) { + HttpRequest httpRequest = null; + try { + httpRequest = new HttpRequest(method, new URI(url)); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return Signer.builder() + .awsCredentials(new AwsCredentials(accessKey, secretKey)) + .region(regionName) + .headers(headers) + .buildS3(httpRequest, payload) + .getSignature(); + } +} diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/createbucket/bucket_b13876524.json b/testhelper/open-sds-junit/src/main/resources/inputs/createbucket/bucket_b13876524.json new file mode 100644 index 000000000..ed3148128 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/resources/inputs/createbucket/bucket_b13876524.json @@ -0,0 +1,5 @@ +{ + "xmlRequestFalse":" falsefalsestring", + "xmlRequestTrue":" truefalsestring", + "xmlPayload":"ibm-back0056" +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/createbucket/ibm-cos_b1321.json b/testhelper/open-sds-junit/src/main/resources/inputs/createbucket/ibm-cos_b1321.json new file mode 100644 index 000000000..2c16d138b --- /dev/null +++ b/testhelper/open-sds-junit/src/main/resources/inputs/createbucket/ibm-cos_b1321.json @@ -0,0 +1,9 @@ +{ + "name": "************", + "type": "************", + "region": "************", + "endpoint": "************", + "bucketName": "************", + "security": "************", + "access": "*************" +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java b/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java new file mode 100644 index 000000000..06cce9906 --- /dev/null +++ b/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java @@ -0,0 +1,123 @@ +import com.google.gson.Gson; +import com.opensds.HttpHandler; +import com.opensds.jsonmodels.akskresponses.SignatureKey; +import com.opensds.jsonmodels.authtokensresponses.AuthTokenHolder; +import com.opensds.jsonmodels.inputs.addbackend.AddBackendInputHolder; +import com.opensds.jsonmodels.inputs.createbucket.CreateBucketFileInput; +import com.opensds.jsonmodels.tokensresponses.TokenHolder; +import com.opensds.jsonmodels.typesresponse.Type; +import com.opensds.jsonmodels.typesresponse.TypesHolder; +import com.opensds.utils.Constant; +import com.opensds.utils.Logger; +import com.opensds.utils.TextUtils; +import com.opensds.utils.Utils; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.XML; +import org.junit.jupiter.api.*; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// how to get POJO from any response JSON, use this site +// http://pojo.sodhanalibrary.com/ + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class CreateBucketBackendTest { + + public static AuthTokenHolder getAuthTokenHolder() { + return mAuthTokenHolder; + } + + public TypesHolder getTypesHolder() { + return mTypesHolder; + } + + public static HttpHandler getHttpHandler() { + return mHttpHandler; + } + + private static AuthTokenHolder mAuthTokenHolder = null; + private static TypesHolder mTypesHolder = null; + private static HttpHandler mHttpHandler = new HttpHandler(); + + @org.junit.jupiter.api.BeforeAll + static void setUp() { + TokenHolder tokenHolder = getHttpHandler().loginAndGetToken(); + mAuthTokenHolder = getHttpHandler().getAuthToken(tokenHolder.getResponseHeaderSubjectToken()); + mTypesHolder = getHttpHandler().getTypes(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + } + + @Test + @Order(1) + @DisplayName("Test creating bucket and backend on OPENSDS") + public void testCreateBucketAndBackend() throws IOException, JSONException { + // load input files for each type and create the backend + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + Gson gson = new Gson(); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + + AddBackendInputHolder inputHolder = gson.fromJson(content, AddBackendInputHolder.class); + int code = getHttpHandler().addBackend(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId(), + inputHolder); + assertEquals(code, 200); + + // backend added, now create buckets + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + // create the bucket specified in each file + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + + CreateBucketFileInput bfi = gson.fromJson(bucketContent, CreateBucketFileInput.class); + + // filename format is "bucket_.json", get the bucket name here + String bName = Utils.getBucketName(bucketFile); + + // now create buckets + int cbCode = getHttpHandler().createBucket(bfi, bName, signatureKey); + assertEquals(cbCode, 200); + Response listBucketResponse = getHttpHandler().getBuckets(signatureKey); + int resCode = listBucketResponse.code(); + String responseBody = listBucketResponse.body().string(); + Logger.logString("Response Code: " + resCode); + Logger.logString("Response: " + responseBody); + JSONObject jsonObject = XML.toJSONObject(responseBody); + JSONArray jsonObjectBucketList = jsonObject.getJSONObject("ListAllMyBucketsResult") + .getJSONObject("Buckets").getJSONArray("Bucket"); + boolean isBucketExist = false; + for (int i = 0; i < jsonObjectBucketList.length(); i++) { + String bucketName = jsonObjectBucketList.getJSONObject(i).get("Name").toString(); + if (!TextUtils.isEmpty(bucketName)) { + if (bucketName.equals(bName)) { + isBucketExist = true; + } + } + } + assertTrue(isBucketExist, "Bucket is not exist: "); + } + } + } + } +} + + From c0482eef8dd889960b67d480d805f407319d0893 Mon Sep 17 00:00:00 2001 From: Click2cloud-Pathak Date: Wed, 20 May 2020 15:48:43 +0530 Subject: [PATCH 2/5] Added test case for negative scenarios for bucket and backend. --- .../main/java/com/opensds/utils/Constant.java | 1 + .../inputs/emptyfield/bucket_b1324.json | 3 + .../inputs/emptyfield/bucket_emptyvalue.json | 3 + .../inputs/emptyfield/ibm-cos_b1321.json | 9 + .../test/java/CreateBucketBackendTest.java | 191 ++++++++++++++++-- 5 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/bucket_b1324.json create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/bucket_emptyvalue.json create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/ibm-cos_b1321.json diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java index d12447b79..8c04989dd 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java @@ -3,4 +3,5 @@ public class Constant { public static final String PATH = System.getenv("INPUT_PATH")+"/src/main/resources/inputs/"; public static final String CREATE_BUCKET_PATH = PATH+"createbucket"; + public static final String EMPTY_FIELD_PATH = PATH+"emptyfield/"; } \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/bucket_b1324.json b/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/bucket_b1324.json new file mode 100644 index 000000000..d8ae96d61 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/bucket_b1324.json @@ -0,0 +1,3 @@ +{ + "xmlPayload":"hdgftr4664" +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/bucket_emptyvalue.json b/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/bucket_emptyvalue.json new file mode 100644 index 000000000..a00c2c861 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/bucket_emptyvalue.json @@ -0,0 +1,3 @@ +{ + "xmlPayload":"" +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/ibm-cos_b1321.json b/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/ibm-cos_b1321.json new file mode 100644 index 000000000..02ad01ad8 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/resources/inputs/emptyfield/ibm-cos_b1321.json @@ -0,0 +1,9 @@ +{ + "name": "", + "type": "", + "region": "", + "endpoint": "", + "bucketName": "", + "security": "", + "access": "" +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java b/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java index 06cce9906..9b1655acd 100644 --- a/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java +++ b/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java @@ -23,8 +23,7 @@ import java.util.List; import static org.junit.Assert.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; // how to get POJO from any response JSON, use this site // http://pojo.sodhanalibrary.com/ @@ -96,28 +95,182 @@ public void testCreateBucketAndBackend() throws IOException, JSONException { // now create buckets int cbCode = getHttpHandler().createBucket(bfi, bName, signatureKey); assertEquals(cbCode, 200); - Response listBucketResponse = getHttpHandler().getBuckets(signatureKey); - int resCode = listBucketResponse.code(); - String responseBody = listBucketResponse.body().string(); - Logger.logString("Response Code: " + resCode); - Logger.logString("Response: " + responseBody); - JSONObject jsonObject = XML.toJSONObject(responseBody); - JSONArray jsonObjectBucketList = jsonObject.getJSONObject("ListAllMyBucketsResult") - .getJSONObject("Buckets").getJSONArray("Bucket"); - boolean isBucketExist = false; - for (int i = 0; i < jsonObjectBucketList.length(); i++) { - String bucketName = jsonObjectBucketList.getJSONObject(i).get("Name").toString(); - if (!TextUtils.isEmpty(bucketName)) { - if (bucketName.equals(bName)) { - isBucketExist = true; - } - } - } + boolean isBucketExist = testGetListBuckets(bName, signatureKey); assertTrue(isBucketExist, "Bucket is not exist: "); } } } } + + public boolean testGetListBuckets(String bName, SignatureKey signatureKey) + throws JSONException, IOException { + Response listBucketResponse = getHttpHandler().getBuckets(signatureKey); + int resCode = listBucketResponse.code(); + String responseBody = listBucketResponse.body().string(); + Logger.logString("Response Code: " + resCode); + Logger.logString("Response: " + responseBody); + JSONObject jsonObject = XML.toJSONObject(responseBody); + JSONArray jsonObjectBucketList = jsonObject.getJSONObject("ListAllMyBucketsResult") + .getJSONObject("Buckets").getJSONArray("Bucket"); + boolean isBucketExist = false; + for (int i = 0; i < jsonObjectBucketList.length(); i++) { + String bucketName = jsonObjectBucketList.getJSONObject(i).get("Name").toString(); + if (!TextUtils.isEmpty(bucketName)) { + if (bucketName.equals(bName)) { + isBucketExist = true; + } + } + } + return isBucketExist; + } + + @Test + @Order(2) + @DisplayName("Test creating bucket using Invalid name") + public void testCreateBucketUsingCapsName() { + // load input files for each type and create the backend + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + Gson gson = new Gson(); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + + // backend added, now create buckets + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + // create the bucket specified in each file + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + + // filename format is "bucket_.json", get the bucket name here + CreateBucketFileInput bfi = gson.fromJson(bucketContent, CreateBucketFileInput.class); + + // now create buckets + int cbCode = getHttpHandler().createBucket(bfi, "RATR_@#", signatureKey); + assertEquals(cbCode, 400); + } + } + } + } + + @Test + @Order(3) + @DisplayName("Test re-creating backend with same name on OPENSDS") + public void testReCreateBackend() { + // load input files for each type and create the backend + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + Gson gson = new Gson(); + // Re-create backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + + AddBackendInputHolder inputHolder = gson.fromJson(content, AddBackendInputHolder.class); + int code = getHttpHandler().addBackend(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId(), + inputHolder); + assertEquals("Re-create backend with same name:Response code not matched:",code, 409); + } + } + } + + @Test + @Order(4) + @DisplayName("Test re-creating bucket with same name on OPENSDS") + public void testReCreateBucket() { + // load input files for each type and create the backend + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + Gson gson = new Gson(); + // Re-create backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + // create the bucket specified in each file + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + + CreateBucketFileInput bfi = gson.fromJson(bucketContent, CreateBucketFileInput.class); + + // filename format is "bucket_.json", get the bucket name here + String bName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, + bucketFile.getName().indexOf(".")); + + // now create buckets + int cbCode = getHttpHandler().createBucket(bfi, bName, signatureKey); + assertEquals("Re-create bucket with same name failed:Response code not matched: " + , cbCode, 409); + } + } + } + } + + @Test + @Order(5) + @DisplayName("Test create bucket with empty name") + public void testCreateBucketWithEmptyName() throws IOException, JSONException { + System.out.println("Verifying response code: Input (Backend name) with empty value in payload and bucket" + + " name is empty"); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + File bucketFile = new File(Constant.EMPTY_FIELD_PATH, "bucket_emptyvalue.json"); + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + Gson gson = new Gson(); + CreateBucketFileInput bfi = gson.fromJson(bucketContent, CreateBucketFileInput.class); + int cbCode = getHttpHandler().createBucket(bfi, "", signatureKey); + assertEquals("Bucket name and backend name is empty in payload :Response code not matched: " + , cbCode, 405); + boolean isBucketExist = testGetListBuckets("", signatureKey); + assertFalse(isBucketExist); + + File file = new File(Constant.EMPTY_FIELD_PATH, "bucket_b1324.json"); + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + String bName = file.getName().substring(bucketFile.getName().indexOf("_") + 1, + bucketFile.getName().indexOf(".")); + + CreateBucketFileInput input = gson.fromJson(content, CreateBucketFileInput.class); + int code = getHttpHandler().createBucket(input, bName, signatureKey); + System.out.println("Verifying response code: In input (Backend name) with not valid value but bucket name is valid"); + assertEquals("Backend does not exist:Response code not matched: " + , code, 404); + } + + @Test + @Order(6) + @DisplayName("Test request body with empty value,try to create backend") + public void testRequestBodyWithEmptyFieldBackend() { + Gson gson = new Gson(); + File file = new File(Constant.EMPTY_FIELD_PATH+"ibm-cos_b1321.json"); + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + AddBackendInputHolder inputHolder = gson.fromJson(content, AddBackendInputHolder.class); + int code = getHttpHandler().addBackend(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId(), + inputHolder); + Logger.logObject("Backend Input: "+content); + assertEquals("Request body with empty value:Response code not matched:",code, 400); + } } From 2c86a05f9ffca019e71c7c150e383e358fc0c055 Mon Sep 17 00:00:00 2001 From: Click2Cloud-Faizan Date: Fri, 22 May 2020 15:41:03 +0530 Subject: [PATCH 3/5] Added test case for verifying backend list and failed backend and bucket scenarios. --- testhelper/open-sds-junit/.gitignore | 1 + .../main/java/com/opensds/HttpHandler.java | 206 ++++----- .../inputs/addbackend/Backends.java | 100 +++++ .../addbackend/BackendsInputHolder.java | 29 ++ .../java/com/opensds/utils/BinaryUtils.java | 134 ++++++ .../main/java/com/opensds/utils/Constant.java | 2 + .../java/com/opensds/utils/ConstantUrl.java | 67 +++ .../java/com/opensds/utils/HeadersName.java | 2 + .../utils/okhttputils/OkHttpRequests.java | 209 +++++++++ .../resources/inputs/rawdata/Screenshot_1.jpg | Bin 0 -> 103609 bytes .../test/java/CreateBucketBackendTest.java | 425 +++++++++++++++++- 11 files changed, 1071 insertions(+), 104 deletions(-) create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/Backends.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/BackendsInputHolder.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/utils/okhttputils/OkHttpRequests.java create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/rawdata/Screenshot_1.jpg diff --git a/testhelper/open-sds-junit/.gitignore b/testhelper/open-sds-junit/.gitignore index 1f6c27656..3fd8b54a1 100644 --- a/testhelper/open-sds-junit/.gitignore +++ b/testhelper/open-sds-junit/.gitignore @@ -10,3 +10,4 @@ **/bin/ **/build/ /environment_variable +/src/main/resources/inputs/download \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java b/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java index a155c66b6..b04b56347 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java @@ -11,29 +11,35 @@ import com.opensds.jsonmodels.logintokensrequests.*; import com.opensds.jsonmodels.tokensresponses.TokenHolder; import com.opensds.jsonmodels.typesresponse.TypesHolder; -import com.opensds.utils.*; -import com.opensds.utils.signature.SodaV4Signer; +import com.opensds.utils.BinaryUtils; +import com.opensds.utils.Constant; +import com.opensds.utils.ConstantUrl; +import com.opensds.utils.Logger; +import com.opensds.utils.okhttputils.OkHttpRequests; import okhttp3.*; -import uk.co.lucasweb.aws.v4.signer.Header; +import okio.BufferedSink; +import okio.Okio; -public class HttpHandler { +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static com.opensds.utils.HeadersName.*; + +public class HttpHandler extends OkHttpRequests { private OkHttpClient client = new OkHttpClient(); - public SignatureKey getAkSkList(String x_auth_token, String userId) { + public SignatureKey getAkSkList(String xAuthToken, String userId) { SignatureKey signatureKey = new SignatureKey(); String url = ConstantUrl.getInstance().getAksList(userId); Logger.logString("URL: " + url); try { Gson gson = new Gson(); - Request request = new Request.Builder() - .url(url) - .get() - .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) - .addHeader(HeadersName.X_AUTH_TOKEN, x_auth_token) - .build(); - Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" - + request.url()); - Response response = client.newCall(request).execute(); + Map headersMap = new HashMap<>(); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + headersMap.put(X_AUTH_TOKEN, xAuthToken); + Headers headers = Headers.of(headersMap); + Response response = getCall(client, url, headers); String responseBody = response.body().string(); Logger.logString("Response: " + responseBody); AKSKHolder akskHolder = gson.fromJson(responseBody, AKSKHolder.class); @@ -62,12 +68,10 @@ public TokenHolder loginAndGetToken() { Auth auth = new Auth(); auth.setIdentity(new Identity()); auth.getIdentity().getMethods().add("password"); - auth.getIdentity().setPassword(new Password()); auth.getIdentity().getPassword().setUser(new User()); auth.getIdentity().getPassword().getUser().setName("admin"); auth.getIdentity().getPassword().getUser().setPassword("opensds@123"); - auth.getIdentity().getPassword().getUser().setDomain(new Domain()); auth.getIdentity().getPassword().getUser().getDomain().setName("Default"); @@ -75,23 +79,19 @@ public TokenHolder loginAndGetToken() { authHolder.setAuth(auth); Gson gson = new Gson(); - RequestBody body = RequestBody.create(gson.toJson(authHolder), + RequestBody requestBody = RequestBody.create(gson.toJson(authHolder), MediaType.parse("application/json; charset=utf-8")); String url = ConstantUrl.getInstance().getTokenLogin(); Logger.logString("URL: " + url); - Request request = new Request.Builder() - .url(url) - .post(body) - .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) - .build(); - Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" - + request.url()); - Response response = client.newCall(request).execute(); + Map headersMap = new HashMap<>(); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + Headers headers = Headers.of(headersMap); + Response response = postCall(client, url, requestBody, headers); String responseBody = response.body().string(); Logger.logString("Response code: " + response.code()); Logger.logString("Response body: " + responseBody); tokenHolder = gson.fromJson(responseBody, TokenHolder.class); - tokenHolder.setResponseHeaderSubjectToken(response.header("X-Subject-Token")); + tokenHolder.setResponseHeaderSubjectToken(response.header(X_SUBJECT_TOKEN)); } catch (Exception e) { e.printStackTrace(); } @@ -116,24 +116,20 @@ public AuthTokenHolder getAuthToken(String x_auth_token) { authHolder.setAuth(auth); Gson gson = new Gson(); - RequestBody body = RequestBody.create(gson.toJson(authHolder), - MediaType.parse("application/json; charset=utf-8")); + RequestBody requestBody = RequestBody.create(gson.toJson(authHolder), + MediaType.parse(CONTENT_TYPE_JSON_CHARSET)); String url = ConstantUrl.getInstance().getTokenLogin(); Logger.logString("URL: " + url); - Request request = new Request.Builder() - .url(url) - .post(body) - .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) - .build(); - Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" - + request.url()); - Response response = client.newCall(request).execute(); + Map headersMap = new HashMap<>(); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + Headers headers = Headers.of(headersMap); + Response response = postCall(client, url, requestBody, headers); String responseBody = response.body().string(); Logger.logString("Response code: " + response.code()); Logger.logString("Response body: " + responseBody); tokenHolder = new com.opensds.jsonmodels.authtokensresponses.AuthTokenHolder(); tokenHolder = gson.fromJson(responseBody, com.opensds.jsonmodels.authtokensresponses.AuthTokenHolder.class); - tokenHolder.setResponseHeaderSubjectToken(response.header("X-Subject-Token")); + tokenHolder.setResponseHeaderSubjectToken(response.header(X_SUBJECT_TOKEN)); } catch (Exception e) { e.printStackTrace(); } @@ -146,14 +142,11 @@ public TypesHolder getTypes(String x_auth_token, String projId) { Gson gson = new Gson(); String url = ConstantUrl.getInstance().getTypesUrl(projId); Logger.logString("URL: " + url); - Request request = new Request.Builder() - .url(url) - .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) - .addHeader(HeadersName.X_AUTH_TOKEN, x_auth_token) - .build(); - Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" - + request.url()); - Response response = client.newCall(request).execute(); + Map headersMap = new HashMap<>(); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + headersMap.put(X_AUTH_TOKEN, x_auth_token); + Headers headers = Headers.of(headersMap); + Response response = getCall(client, url, headers); String responseBody = response.body().string(); Logger.logString("Response code: " + response.code()); Logger.logString("Response body: " + responseBody); @@ -168,22 +161,17 @@ public int addBackend(String x_auth_token, String projId, AddBackendInputHolder int code = -1; try { Gson gson = new Gson(); - RequestBody body = RequestBody.create(gson.toJson(inputHolder), - MediaType.parse("application/json; charset=utf-8")); + RequestBody requestBody = RequestBody.create(gson.toJson(inputHolder), + MediaType.parse(CONTENT_TYPE_JSON_CHARSET)); String url = ConstantUrl.getInstance().getAddBackendUrl(projId); Logger.logString("URL: " + url); - Request request = new Request.Builder() - .url(url) - .post(body) - .addHeader(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_JSON) - .addHeader(HeadersName.X_AUTH_TOKEN, x_auth_token) - .build(); - Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" - + request.url()); - Response response = client.newCall(request).execute(); + Map headersMap = new HashMap<>(); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + headersMap.put(X_AUTH_TOKEN, x_auth_token); + Headers headers = Headers.of(headersMap); + Response response = postCall(client, url, requestBody, headers); code = response.code(); Logger.logString("Response code: " + code); - Logger.logString("Response body: " + response.body().string()); } catch (Exception e) { e.printStackTrace(); } @@ -193,33 +181,33 @@ public int addBackend(String x_auth_token, String projId, AddBackendInputHolder public int createBucket(CreateBucketFileInput input, String bucketName, SignatureKey signatureKey) { int code = -1; try { - RequestBody body = RequestBody.create(input.getXmlPayload(), - MediaType.parse("application/xml")); - String payload = BinaryUtils.toHex(BinaryUtils.hash(input.getXmlPayload())); - String date = Utils.getDate(); String url = ConstantUrl.getInstance().getCreateBucketUrl(bucketName); - Logger.logString("URL: " + url); - String host = Utils.getHost(url); - Header[] headerList = new Header[]{new Header(HeadersName.HOST, host), - new Header(HeadersName.X_AMZ_DATE, date), - new Header(HeadersName.X_AMZ_CONTENT_SHA256, payload)}; - String authorization = SodaV4Signer.getSignature("PUT", url, signatureKey.getAccessKey(), - signatureKey.getSecretAccessKey(), payload, signatureKey.getRegionName(), headerList); - Logger.logString("Authorization: " + authorization); + RequestBody requestBody = RequestBody.create(input.getXmlPayload(), + MediaType.parse(CONTENT_TYPE_XML)); + String payload = BinaryUtils.toHex(BinaryUtils.hash(input.getXmlPayload())); + Response response = putCallResponse(client, url, payload, requestBody, signatureKey); + code = response.code(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + response.body().string()); + } catch (Exception e) { + e.printStackTrace(); + } + return code; + } - Request request = new Request.Builder() - .url(url) - .put(body) - .header(HeadersName.HOST, host) - .header(HeadersName.AUTHORIZATION, authorization) - .header(HeadersName.X_AMZ_CONTENT_SHA256, payload) - .header(HeadersName.CONTENT_TYPE, HeadersName.CONTENT_TYPE_XML) - .header(HeadersName.X_AMZ_DATE, date) - .build(); + public Response getBuckets(SignatureKey signatureKey) { + String url = ConstantUrl.getInstance().getListBucketUrl(); + return getCallResponse(client, url, signatureKey); + } - Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" - + request.url()); - Response response = client.newCall(request).execute(); + public int uploadObject(SignatureKey signatureKey, String bucketName, String fileName, File mFilePath) { + int code = -1; + try { + RequestBody requestBody = RequestBody.create(mFilePath, + MediaType.parse(CONTENT_TYPE_XML)); + String url = ConstantUrl.getInstance().getUploadObjectUrl(bucketName, fileName); + String payload = BinaryUtils.toHex(BinaryUtils.computeSHA256TreeHash(mFilePath)); + Response response = putCallResponse(client, url, payload, requestBody, signatureKey); code = response.code(); Logger.logString("Response Code: " + code); Logger.logString("Response: " + response.body().string()); @@ -229,33 +217,45 @@ public int createBucket(CreateBucketFileInput input, String bucketName, Signatur return code; } - public Response getBuckets(SignatureKey signatureKey) { + public Response getBucketObjects(String bucketName, SignatureKey signatureKey) { + String url = ConstantUrl.getInstance().getListOfBucketObjectUrl(bucketName); + return getCallResponse(client, url, signatureKey); + } + + public Response downloadObject(SignatureKey signatureKey, String bucketName, String fileName, String downloadFileName) { Response response = null; - String url = ConstantUrl.getInstance().getListBucketUrl(); - String payload = BinaryUtils.toHex(BinaryUtils.hash("")); - String date = Utils.getDate(); - Logger.logString("URL: " + url); - String host = Utils.getHost(url); - Header[] headerList = new Header[]{new Header(HeadersName.HOST, host), - new Header(HeadersName.X_AMZ_DATE, date), - new Header(HeadersName.X_AMZ_CONTENT_SHA256, payload)}; - String authorization = SodaV4Signer.getSignature("GET", url, signatureKey.getAccessKey(), - signatureKey.getSecretAccessKey(), payload, signatureKey.getRegionName(), headerList); - Logger.logString("Authorization: " + authorization); try { - Request request = new Request.Builder() - .url(url) - .get() - .addHeader(HeadersName.AUTHORIZATION, authorization) - .addHeader(HeadersName.X_AMZ_CONTENT_SHA256, payload) - .addHeader(HeadersName.X_AMZ_DATE, date) - .build(); - Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" - + request.url()); - response = client.newCall(request).execute(); + String url = ConstantUrl.getInstance().getDownloadObjectUrl(bucketName, fileName); + response = getCallResponse(client, url, signatureKey); + int code = response.code(); + if (code == 200) { + BufferedSink sink = Okio.buffer(Okio.sink(new File(Constant.DOWNLOAD_FILES_PATH, downloadFileName))); + sink.writeAll(response.body().source()); + sink.close(); + } } catch (Exception e) { e.printStackTrace(); } return response; } + + public Response getBackends(String xAuthToken, String tenantId) { + String url = ConstantUrl.getInstance().getBackendsUrl(tenantId); + return getCallWithXauth(client, url, xAuthToken); + } + + public Response getBackend(String xAuthToken, String tenantId, String id) { + String url = ConstantUrl.getInstance().getBackendUrl(tenantId, id); + return getCallWithXauth(client, url, xAuthToken); + } + + public Response getDeleteBackend(String xAuthToken, String tenantId, String id) { + String url = ConstantUrl.getInstance().getDeleteBackendUrl(tenantId, id); + return deleteCallWithXauth(client, url, xAuthToken); + } + + public Response deleteBucketNotEmpty(SignatureKey signatureKey, String bucketName) { + String url = ConstantUrl.getInstance().getDeleteBucketUrl(bucketName); + return deleteCallWithV4Sign(client, url, signatureKey); + } } \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/Backends.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/Backends.java new file mode 100644 index 000000000..8a456bf25 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/Backends.java @@ -0,0 +1,100 @@ +package com.opensds.jsonmodels.inputs.addbackend; + +public class Backends +{ + private String bucketName; + + private String endpoint; + + private String tenantId; + + private String name; + + private String id; + + private String type; + + private String region; + + private String userId; + + public String getBucketName () + { + return bucketName; + } + + public void setBucketName (String bucketName) + { + this.bucketName = bucketName; + } + + public String getEndpoint () + { + return endpoint; + } + + public void setEndpoint (String endpoint) + { + this.endpoint = endpoint; + } + + public String getTenantId () + { + return tenantId; + } + + public void setTenantId (String tenantId) + { + this.tenantId = tenantId; + } + + public String getName () + { + return name; + } + + public void setName (String name) + { + this.name = name; + } + + public String getId () + { + return id; + } + + public void setId (String id) + { + this.id = id; + } + + public String getType () + { + return type; + } + + public void setType (String type) + { + this.type = type; + } + + public String getRegion () + { + return region; + } + + public void setRegion (String region) + { + this.region = region; + } + + public String getUserId () + { + return userId; + } + + public void setUserId (String userId) + { + this.userId = userId; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/BackendsInputHolder.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/BackendsInputHolder.java new file mode 100644 index 000000000..98412e84c --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/addbackend/BackendsInputHolder.java @@ -0,0 +1,29 @@ +package com.opensds.jsonmodels.inputs.addbackend; + +import java.util.ArrayList; + +public class BackendsInputHolder +{ + private ArrayList backends; + private String next; + + public String getNext () + { + return next; + } + + public void setNext (String next) + { + this.next = next; + } + + public ArrayList getBackends () + { + return backends; + } + + public void setBackends (ArrayList backends) + { + this.backends = backends; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/BinaryUtils.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/BinaryUtils.java index 1fdbd4086..c99f08e13 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/BinaryUtils.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/BinaryUtils.java @@ -1,12 +1,17 @@ package com.opensds.utils; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Locale; /** * Utilities for encoding and decoding binary data to and from different forms. */ public class BinaryUtils { + static final int ONE_MB = 1024 * 1024; /** * Converts byte data to a Hex-encoded string. @@ -65,4 +70,133 @@ public static byte[] hash(String text) { throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e); } } + + /** + * Computes the SHA-256 tree hash for the given file + * + * @param inputFile + * a File to compute the SHA-256 tree hash for + * @return a byte[] containing the SHA-256 tree hash + * @throws IOException + * Thrown if there's an issue reading the input file + * @throws NoSuchAlgorithmException + */ + public static byte[] computeSHA256TreeHash(File inputFile) throws IOException, + NoSuchAlgorithmException { + + byte[][] chunkSHA256Hashes = getChunkSHA256Hashes(inputFile); + return computeSHA256TreeHash(chunkSHA256Hashes); + } + + /** + * Computes a SHA256 checksum for each 1 MB chunk of the input file. This + * includes the checksum for the last chunk even if it is smaller than 1 MB. + * + * @param file + * A file to compute checksums on + * @return a byte[][] containing the checksums of each 1 MB chunk + * @throws IOException + * Thrown if there's an IOException when reading the file + * @throws NoSuchAlgorithmException + * Thrown if SHA-256 MessageDigest can't be found + */ + public static byte[][] getChunkSHA256Hashes(File file) throws IOException, + NoSuchAlgorithmException { + + MessageDigest md = MessageDigest.getInstance("SHA-256"); + + long numChunks = file.length() / ONE_MB; + if (file.length() % ONE_MB > 0) { + numChunks++; + } + + if (numChunks == 0) { + return new byte[][] { md.digest() }; + } + + byte[][] chunkSHA256Hashes = new byte[(int) numChunks][]; + FileInputStream fileStream = null; + + try { + fileStream = new FileInputStream(file); + byte[] buff = new byte[ONE_MB]; + + int bytesRead; + int idx = 0; + + while ((bytesRead = fileStream.read(buff, 0, ONE_MB)) > 0) { + md.reset(); + md.update(buff, 0, bytesRead); + chunkSHA256Hashes[idx++] = md.digest(); + } + + return chunkSHA256Hashes; + + } finally { + if (fileStream != null) { + try { + fileStream.close(); + } catch (IOException ioe) { + System.err.printf("Exception while closing %s.\n %s", file.getName(), + ioe.getMessage()); + } + } + } + } + + /** + * Computes the SHA-256 tree hash for the passed array of 1 MB chunk + * checksums. + * + * This method uses a pair of arrays to iteratively compute the tree hash + * level by level. Each iteration takes two adjacent elements from the + * previous level source array, computes the SHA-256 hash on their + * concatenated value and places the result in the next level's destination + * array. At the end of an iteration, the destination array becomes the + * source array for the next level. + * + * @param chunkSHA256Hashes + * An array of SHA-256 checksums + * @return A byte[] containing the SHA-256 tree hash for the input chunks + * @throws NoSuchAlgorithmException + * Thrown if SHA-256 MessageDigest can't be found + */ + public static byte[] computeSHA256TreeHash(byte[][] chunkSHA256Hashes) + throws NoSuchAlgorithmException { + + MessageDigest md = MessageDigest.getInstance("SHA-256"); + + byte[][] prevLvlHashes = chunkSHA256Hashes; + + while (prevLvlHashes.length > 1) { + + int len = prevLvlHashes.length / 2; + if (prevLvlHashes.length % 2 != 0) { + len++; + } + + byte[][] currLvlHashes = new byte[len][]; + + int j = 0; + for (int i = 0; i < prevLvlHashes.length; i = i + 2, j++) { + + // If there are at least two elements remaining + if (prevLvlHashes.length - i > 1) { + + // Calculate a digest of the concatenated nodes + md.reset(); + md.update(prevLvlHashes[i]); + md.update(prevLvlHashes[i + 1]); + currLvlHashes[j] = md.digest(); + + } else { // Take care of remaining odd chunk + currLvlHashes[j] = prevLvlHashes[i]; + } + } + + prevLvlHashes = currLvlHashes; + } + + return prevLvlHashes[0]; + } } diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java index 8c04989dd..f28ba7341 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java @@ -4,4 +4,6 @@ public class Constant { public static final String PATH = System.getenv("INPUT_PATH")+"/src/main/resources/inputs/"; public static final String CREATE_BUCKET_PATH = PATH+"createbucket"; public static final String EMPTY_FIELD_PATH = PATH+"emptyfield/"; + public static final String RAW_DATA_PATH = PATH+"rawdata"; + public static final String DOWNLOAD_FILES_PATH = PATH+"download"; } \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java index 4cd2d5fe1..1b5ed0c85 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java @@ -90,4 +90,71 @@ public String getCreateBucketUrl(String bucketName) { public String getListBucketUrl() { return URL+PORT+"/"; } + + /** + * Upload object + * + * @param bucketName bucket name. + * @param fileName file name. + */ + public String getUploadObjectUrl(String bucketName, String fileName) { + return URL+PORT+"/"+ bucketName +"/"+ fileName; + } + + /** + * Get list of bucket object. + * + * @param bucketName bucket name + */ + public String getListOfBucketObjectUrl(String bucketName) { + return URL+PORT+"/"+bucketName; + } + + /** + * Download object + * + * @param bucketName bucket name. + * @param fileName file name. + */ + public String getDownloadObjectUrl(String bucketName, String fileName) { + return URL+PORT+"/"+ bucketName +"/"+ fileName; + } + + /** + * Get Backend List + * + * @param tenantId tenant id. + */ + public String getBackendsUrl(String tenantId) { + return URL+PORT_TENANT_ID+"/"+tenantId+"/backends"; + } + + /** + * Get Backend + * + * @param tenantId tenant id. + * @param id admin tenant id. + */ + public String getBackendUrl(String tenantId, String id) { + return URL+PORT_TENANT_ID+"/"+tenantId+"/backends/"+id; + } + + /** + * Delete Backend + * + * @param tenantId tenant id. + * @param id admin tenant id. + */ + public String getDeleteBackendUrl(String tenantId, String id) { + return URL+PORT_TENANT_ID+"/"+tenantId+"/backends/"+id; + } + + /** + * Delete bucket + * + * @param bucketName bucket name. + */ + public String getDeleteBucketUrl(String bucketName) { + return URL+PORT+"/"+ bucketName; + } } diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/HeadersName.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/HeadersName.java index 9677f769f..f5ced627b 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/HeadersName.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/HeadersName.java @@ -9,4 +9,6 @@ public class HeadersName { public static final String X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256"; public static final String X_AUTH_TOKEN = "x-auth-token"; public static final String HOST = "Host"; + public static final String X_SUBJECT_TOKEN = "X-Subject-Token"; + public static final String CONTENT_TYPE_JSON_CHARSET = "application/json; charset=utf-8"; } diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/okhttputils/OkHttpRequests.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/okhttputils/OkHttpRequests.java new file mode 100644 index 000000000..0473c867d --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/okhttputils/OkHttpRequests.java @@ -0,0 +1,209 @@ +package com.opensds.utils.okhttputils; + +import com.opensds.jsonmodels.akskresponses.SignatureKey; +import com.opensds.utils.*; +import com.opensds.utils.signature.SodaV4Signer; +import okhttp3.*; +import uk.co.lucasweb.aws.v4.signer.Header; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static com.opensds.utils.HeadersName.*; + +public abstract class OkHttpRequests { + + /** + * Put Call + * + * @param client OkHttpClient object + * @param url url + * @param requestBody RequestBody object + * @param headers Headers + * @return response + * @throws IOException io exception + */ + protected static Response putCall(OkHttpClient client, String url, RequestBody requestBody, + Headers headers) throws IOException { + Request request = new Request.Builder() + .url(url) + .put(requestBody) + .headers(headers) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + return client.newCall(request).execute(); + } + + /** + * Post Call + * + * @param client OkHttpClient object + * @param url url + * @param requestBody RequestBody object + * @param headers Headers + * @return Response + * @throws IOException IO Exception + */ + protected static Response postCall(OkHttpClient client, String url, RequestBody requestBody, + Headers headers) throws IOException { + Request request = new Request.Builder() + .url(url) + .post(requestBody) + .headers(headers) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + return client.newCall(request).execute(); + } + + /** + * Get Call + * + * @param client OkHttpClient + * @param url URL + * @param headers Headers + * @return Response + * @throws IOException IO Exception + */ + protected static Response getCall(OkHttpClient client, String url, + Headers headers) throws IOException { + Request request = new Request.Builder() + .url(url) + .get() + .headers(headers) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + return client.newCall(request).execute(); + } + + /** + * Delete Call + * + * @param client OkHttpClient + * @param url URL + * @param headers Headers + * @return Response + * @throws IOException IO Exception + */ + protected static Response deleteCall(OkHttpClient client, String url, + Headers headers) throws IOException { + Request request = new Request.Builder() + .url(url) + .delete() + .headers(headers) + .build(); + Logger.logString("Request Details: " + request.headers() + " " + request.body() + " " + request.method() + "" + + request.url()); + return client.newCall(request).execute(); + } + + protected static Response getCallResponse(OkHttpClient client, String url, SignatureKey signatureKey) { + Response response = null; + String payload = BinaryUtils.toHex(BinaryUtils.hash("")); + String date = Utils.getDate(); + Logger.logString("URL: " + url); + String host = Utils.getHost(url); + Header[] signHeaders = new Header[]{new Header(HOST, host), + new Header(X_AMZ_DATE, date), + new Header(X_AMZ_CONTENT_SHA256, payload)}; + String authorization = SodaV4Signer.getSignature("GET", url, signatureKey.getAccessKey(), + signatureKey.getSecretAccessKey(), payload, signatureKey.getRegionName(), signHeaders); + Logger.logString("Authorization: " + authorization); + Map headersMap = new HashMap<>(); + headersMap.put(AUTHORIZATION, authorization); + headersMap.put(X_AMZ_CONTENT_SHA256, payload); + headersMap.put(X_AMZ_DATE, date); + Headers headers = Headers.of(headersMap); + try { + response = getCall(client, url, headers); + } catch (Exception e) { + e.printStackTrace(); + } + return response; + } + + protected static Response putCallResponse(OkHttpClient client, String url, + String payload, RequestBody requestBody, + SignatureKey signatureKey) { + Response response = null; + + String date = Utils.getDate(); + Logger.logString("URL: " + url); + String host = Utils.getHost(url); + Header[] signHeaders = new Header[]{new Header(HOST, host), + new Header(X_AMZ_DATE, date), + new Header(X_AMZ_CONTENT_SHA256, payload)}; + String authorization = SodaV4Signer.getSignature("PUT", url, signatureKey.getAccessKey(), + signatureKey.getSecretAccessKey(), payload, signatureKey.getRegionName(), signHeaders); + Logger.logString("Authorization: " + authorization); + Map headersMap = new HashMap<>(); + headersMap.put(HOST, host); + headersMap.put(AUTHORIZATION, authorization); + headersMap.put(X_AMZ_CONTENT_SHA256, payload); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_XML); + headersMap.put(X_AMZ_DATE, date); + Headers headers = Headers.of(headersMap); + try { + response = putCall(client, url, requestBody, headers); + } catch (IOException e) { + e.printStackTrace(); + } + return response; + } + + protected static Response getCallWithXauth(OkHttpClient client, String url, String xAuthToken){ + Response response = null; + Map headersMap = new HashMap<>(); + headersMap.put(X_AUTH_TOKEN, xAuthToken); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + Headers headers = Headers.of(headersMap); + try { + response = getCall(client, url, headers); + } catch (Exception e) { + e.printStackTrace(); + } + return response; + } + + protected static Response deleteCallWithXauth(OkHttpClient client, String url, String xAuthToken){ + Response response = null; + Map headersMap = new HashMap<>(); + headersMap.put(X_AUTH_TOKEN, xAuthToken); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + Headers headers = Headers.of(headersMap); + try { + response = deleteCall(client, url, headers); + } catch (Exception e) { + e.printStackTrace(); + } + return response; + } + + protected static Response deleteCallWithV4Sign(OkHttpClient client, String url, SignatureKey signatureKey) { + Response response = null; + String payload = BinaryUtils.toHex(BinaryUtils.hash("")); + String date = Utils.getDate(); + Logger.logString("URL: " + url); + String host = Utils.getHost(url); + Header[] signHeaders = new Header[]{new Header(HOST, host), + new Header(X_AMZ_DATE, date), + new Header(X_AMZ_CONTENT_SHA256, payload)}; + String authorization = SodaV4Signer.getSignature("DELETE", url, signatureKey.getAccessKey(), + signatureKey.getSecretAccessKey(), payload, signatureKey.getRegionName(), signHeaders); + Logger.logString("Authorization: " + authorization); + Map headersMap = new HashMap<>(); + headersMap.put(AUTHORIZATION, authorization); + headersMap.put(X_AMZ_CONTENT_SHA256, payload); + headersMap.put(X_AMZ_DATE, date); + Headers headers = Headers.of(headersMap); + try { + response = deleteCall(client, url, headers); + } catch (Exception e) { + e.printStackTrace(); + } + return response; + } +} diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/rawdata/Screenshot_1.jpg b/testhelper/open-sds-junit/src/main/resources/inputs/rawdata/Screenshot_1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0fd5f53f9fdd08c7fb85ed6cd7ba7dccf0ccc5ad GIT binary patch literal 103609 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

6|8z?S;6ASLpN<$J1jxvt%O`;L=? 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%0Dqesh39HBL$V`vI3FGHxxRoz2p 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Π+))i}}to$`p=`cY<6)F2h@X;I)FRVR9+RoRDx*i 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 listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + // Get bucket name. + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + String bucketName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, + bucketFile.getName().indexOf(".")); + // Get object for upload. + File fileRawData = new File(Constant.RAW_DATA_PATH); + File[] files = fileRawData.listFiles(); + String mFileName = null; + File mFilePath = null; + for (File fileName : files) { + mFileName = fileName.getName(); + mFilePath = fileName; + + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + int cbCode = getHttpHandler().uploadObject(signatureKey, + bucketName, mFileName, mFilePath); + assertEquals("Uploaded object failed", cbCode, 200); + + //Verifying object is uploaded in bucket. + boolean isUploaded = testGetListOfObjectFromBucket(bucketName, mFileName, signatureKey); + assertTrue(isUploaded,"Object is not uploaded"); + } + } + } + } + } + + /** + * Get List of object from bucket + * + * @param bucketName bucket name + * @param fileName file name + * @param signatureKey signature key object + * @return boolean object is upload then true else false + * @throws JSONException json exception + * @throws IOException io exception + */ + private boolean testGetListOfObjectFromBucket(String bucketName, String fileName, + SignatureKey signatureKey) + throws JSONException, IOException { + Response listObjectResponse = getHttpHandler().getBucketObjects(bucketName, signatureKey); + int resCode = listObjectResponse.code(); + String resBody = listObjectResponse.body().string(); + Logger.logString("Response Code: " + resCode); + Logger.logString("Response: " + resBody); + assertEquals("Get list of object failed", resCode, 200); + JSONObject jsonObject = XML.toJSONObject(resBody); + JSONObject jsonObjectListBucket = jsonObject.getJSONObject("ListBucketResult"); + boolean isUploaded = false; + if (jsonObjectListBucket.has("Contents")) { + if (jsonObjectListBucket.get("Contents") instanceof JSONArray) { + JSONArray objects = jsonObjectListBucket.getJSONArray("Contents"); + for (int i = 0; i < objects.length(); i++) { + if (!TextUtils.isEmpty(objects.getJSONObject(i).get("Key").toString())) { + if (objects.getJSONObject(i).get("Key").toString().equals(fileName)) { + isUploaded = true; + } + } + } + } else { + if (!TextUtils.isEmpty(jsonObjectListBucket.getJSONObject("Contents") + .get("Key").toString())) { + if (jsonObjectListBucket.getJSONObject("Contents").get("Key").toString() + .equals(fileName)) { + isUploaded = true; + } + } + } + } + return isUploaded; + } + + @Test + @Order(8) + @DisplayName("Test uploading object failed scenario") + public void testUploadObjectFailed(){ + File fileRawData = new File(Constant.RAW_DATA_PATH); + File[] files = fileRawData.listFiles(); + String mFileName = null; + File mFilePath = null; + for (File fileName : files) { + mFileName = fileName.getName(); + mFilePath = fileName; + } + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + int cbCode = getHttpHandler().uploadObject(signatureKey, + "bucketName", mFileName, mFilePath); + System.out.println("Verifying Upload object with non existing bucket"); + assertEquals("Upload object with non existing bucket: Response code not matched", cbCode, 404); + + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + // Get bucket name. + for (File bucketFile : listOfIBucketInputs) { + String bucketName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, + bucketFile.getName().indexOf(".")); + int code = getHttpHandler().uploadObject(signatureKey, + bucketName, "", mFilePath); + System.out.println("Verifying upload object in existing bucket with file name is empty"); + assertEquals("Upload object with existing bucket with file name empty: Response code not matched" + , code, 400); + } + } + + @Test + @Order(9) + @DisplayName("Test verifying download non exist file") + public void testDownloadNonExistFile() throws IOException { + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + String fileName = "download_image.jpg"; + String bucketName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, + bucketFile.getName().indexOf(".")); + Response response = getHttpHandler().downloadObject(signatureKey, + bucketName, "23455", fileName); + int code = response.code(); + String body = response.body().string(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + body); + assertEquals("Downloading non exist file: ", code, 404); + } + } + + @Test + @Order(10) + @DisplayName("Test verifying download file from non exist bucket") + public void testDownloadFileFromNonExistBucket() throws IOException { + String dFileName = "download_image.jpg"; + File fileRawData = new File(Constant.RAW_DATA_PATH); + File[] files = fileRawData.listFiles(); + String mFileName = null; + for (File fileName : files) { + mFileName = fileName.getName(); + } + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + Response response = getHttpHandler().downloadObject(signatureKey, + "hfhfhd", mFileName, dFileName); + int code = response.code(); + String body = response.body().string(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + body); + assertEquals("Downloading file from non exist bucket: ", code, 404); + } + + @Test + @Order(11) + @DisplayName("Test verifying download file from non exist bucket and file name") + public void testDownloadNonExistBucketAndFile() throws IOException { + String fileName = "download_image.jpg"; + System.out.println("Verifying download file from non exist bucket and file name"); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + Response response = getHttpHandler().downloadObject(signatureKey, + "ghjhb", "yuyiyh", fileName); + int code = response.code(); + String body = response.body().string(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + body); + assertEquals("Downloading file from non exist bucket and file name: ", code, 404); + assertEquals("Response message is not valid, bucket and filename not exist: ", body); + } + + @Test + @Order(12) + @DisplayName("Test downloading object in a folder") + public void testDownloadObject() throws IOException { + // load input files for each type + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + // backend added, now create buckets + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + String bucketName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, + bucketFile.getName().indexOf(".")); + // Get object for upload. + File fileRawData = new File(Constant.RAW_DATA_PATH); + File[] files = fileRawData.listFiles(); + String mFileName = null; + for (File fileName : files) { + mFileName = fileName.getName(); + } + String fileName = "download_image.jpg"; + File filePath = new File(Constant.DOWNLOAD_FILES_PATH); + File downloadedFile = new File(Constant.DOWNLOAD_FILES_PATH, fileName); + if (filePath.exists()) { + if (downloadedFile.exists()) { + boolean isDownloadedFileDeleted = downloadedFile.delete(); + assertTrue(isDownloadedFileDeleted, "Image deleting is failed"); + } else { + assertFalse(downloadedFile.exists()); + } + } else { + filePath.mkdirs(); + } + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + Response response = getHttpHandler().downloadObject(signatureKey, + bucketName, mFileName, fileName); + int code = response.code(); + String body = response.body().string(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + body); + assertEquals("Downloading failed", code, 200); + assertTrue(downloadedFile.isFile(), "Downloaded Image is not available"); + } + } + } + } + + @Test + @Order(13) + @DisplayName("Test verifying backends list and single backend") + public void testAddBackendGetBackends() throws IOException { + // load input files for each type. + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + Gson gson = new Gson(); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + AddBackendInputHolder inputHolder = gson.fromJson(content, AddBackendInputHolder.class); + // Get backend list + Response response = getHttpHandler().getBackends(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + int code = response.code(); + String responseBody = response.body().string(); + Logger.logString("Response Code: " + code); + assertEquals("Get Backends List Failed: ",code, 200); + assertNotNull(responseBody); + BackendsInputHolder backendsInputHolder = gson.fromJson(responseBody, + BackendsInputHolder.class); + // Filter backend + List backendFilter = backendsInputHolder.getBackends().stream() + .filter(p -> !TextUtils.isEmpty(p.getName())) + .collect(Collectors.toList()); + + List backend = backendFilter.stream() + .filter(p -> p.getName().equals(inputHolder.getName())) + .collect(Collectors.toList()); + + assertNotNull(backend); + + // Get backend + for (int i = 0; i < backend.size(); i++) { + Response responseBackend = getHttpHandler().getBackend(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken() + .getProject().getId(), backend.get(i).getId()); + int resCode = response.code(); + String responseBackendBody = responseBackend.body().string(); + Logger.logString("Response Code: " + resCode); + assertEquals(resCode, 200); + assertNotNull(responseBackendBody); + Backends backends = gson.fromJson(responseBackendBody, Backends.class); + assertNotNull(backends); + assertEquals("Backend name not matched: ", backends.getName(), inputHolder.getName()); + } + } + } + } + + @Test + @Order(14) + @DisplayName("Test verifying non exist backend") + public void testNonExistBackend() { + Response responseBackend = getHttpHandler().getBackend(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId(), "reuiu5475"); + int code = responseBackend.code(); + Logger.logString("Response Code: " + code); + assertEquals("Get backend failed:Response code not matched: ", code, 400); + } + + @Test + @Order(15) + @DisplayName("Test verifying delete non empty backend") + public void testDeleteNonEmptyBackend() throws IOException { + // load input files for each type. + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + Gson gson = new Gson(); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + AddBackendInputHolder inputHolder = gson.fromJson(content, AddBackendInputHolder.class); + + // Get backend list + Response response = getHttpHandler().getBackends(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + int code = response.code(); + Logger.logString("Response Code: " + code); + assertEquals(code, 200); + String responseBody = response.body().string(); + assertNotNull(responseBody); + BackendsInputHolder backendsInputHolder = gson.fromJson(responseBody, + BackendsInputHolder.class); + // Filter backend + List backendFilter = backendsInputHolder.getBackends().stream() + .filter(p -> !TextUtils.isEmpty(p.getName())) + .collect(Collectors.toList()); + + List backend = backendFilter.stream() + .filter(p -> p.getName().equals(inputHolder.getName())) + .collect(Collectors.toList()); + assertNotNull(backend); + + // Get backend + for (int i = 0; i < backend.size(); i++) { + Response responseBackend = getHttpHandler().getBackend(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken() + .getProject().getId(), backend.get(i).getId()); + int resCode = response.code(); + String responseBackendBody = responseBackend.body().string(); + Logger.logString("Response Code: " + resCode); + assertEquals(responseBackend.code(), 200); + assertNotNull(responseBackendBody); + Backends backends = gson.fromJson(responseBackendBody, Backends.class); + assertNotNull(backends); + assertEquals(backends.getName(), inputHolder.getName()); + Response responseDeleteBackend= getHttpHandler().getDeleteBackend(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken() + .getProject().getId(), backend.get(i).getId()); + int responseCode = responseDeleteBackend.code(); + String resp = responseDeleteBackend.body().string(); + Logger.logString("Response Code: " + responseCode); + Logger.logString("Response: " + resp); + assertEquals("Deleting Non empty backend:Response code not matched: " + ,responseCode, 409); + } + } + } + } + + @Test + @Order(16) + @DisplayName("Test deleting non empty bucket") + public void testDeleteNonEmptyBucket() throws IOException { + // load input files for each type. + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + // create the bucket specified in each file + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + + // filename format is "bucket_.json", get the bucket name here + String bName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, + bucketFile.getName().indexOf(".")); + // Verifying Bucket not empty + Response response = getHttpHandler().deleteBucketNotEmpty(signatureKey, bName); + int responseCode = response.code(); + String responseBody = response.body().string(); + Logger.logString("Response Code: "+responseCode); + Logger.logString("Response: "+responseBody); + assertEquals("Verifying Bucket not empty: ", responseCode, 409); + } + } + } + } } From 27a3c090680f942e8e91e0abb35770589eb270e9 Mon Sep 17 00:00:00 2001 From: click2cloud-ghosh Date: Fri, 22 May 2020 19:48:15 +0530 Subject: [PATCH 4/5] Added test case for delete bucket and backend with failed scenarios. Also used common code for get bucket name. --- .../main/java/com/opensds/HttpHandler.java | 10 + .../java/com/opensds/utils/ConstantUrl.java | 10 + .../utils/okhttputils/OkHttpRequests.java | 2 + .../test/java/CreateBucketBackendTest.java | 189 ++++++++++++++++-- 4 files changed, 199 insertions(+), 12 deletions(-) diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java b/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java index b04b56347..4607c2ed6 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java @@ -258,4 +258,14 @@ public Response deleteBucketNotEmpty(SignatureKey signatureKey, String bucketNam String url = ConstantUrl.getInstance().getDeleteBucketUrl(bucketName); return deleteCallWithV4Sign(client, url, signatureKey); } + + public Response deleteObject(SignatureKey signatureKey, String bucketName, String objectName) { + String url = ConstantUrl.getInstance().getDeleteObjectUrl(bucketName, objectName); + return deleteCallWithV4Sign(client, url, signatureKey); + } + + public Response deleteBucket(SignatureKey signatureKey, String bucketName) { + String url = ConstantUrl.getInstance().getDeleteBucketUrl(bucketName); + return deleteCallWithV4Sign(client, url, signatureKey); + } } \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java index 1b5ed0c85..6f1ea5746 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java @@ -157,4 +157,14 @@ public String getDeleteBackendUrl(String tenantId, String id) { public String getDeleteBucketUrl(String bucketName) { return URL+PORT+"/"+ bucketName; } + + /** + * Delete object + * + * @param bucketName bucket name. + * @param objectName object name. + */ + public String getDeleteObjectUrl(String bucketName, String objectName) { + return URL+PORT+"/"+bucketName+"/"+objectName; + } } diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/okhttputils/OkHttpRequests.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/okhttputils/OkHttpRequests.java index 0473c867d..474bf357a 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/okhttputils/OkHttpRequests.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/okhttputils/OkHttpRequests.java @@ -156,6 +156,7 @@ protected static Response putCallResponse(OkHttpClient client, String url, protected static Response getCallWithXauth(OkHttpClient client, String url, String xAuthToken){ Response response = null; + Logger.logString("URL: "+url); Map headersMap = new HashMap<>(); headersMap.put(X_AUTH_TOKEN, xAuthToken); headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); @@ -170,6 +171,7 @@ protected static Response getCallWithXauth(OkHttpClient client, String url, Stri protected static Response deleteCallWithXauth(OkHttpClient client, String url, String xAuthToken){ Response response = null; + Logger.logString("URL: "+url); Map headersMap = new HashMap<>(); headersMap.put(X_AUTH_TOKEN, xAuthToken); headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); diff --git a/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java b/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java index dd29aacea..f40b28a59 100644 --- a/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java +++ b/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java @@ -224,8 +224,7 @@ public void testReCreateBucket() { CreateBucketFileInput bfi = gson.fromJson(bucketContent, CreateBucketFileInput.class); // filename format is "bucket_.json", get the bucket name here - String bName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, - bucketFile.getName().indexOf(".")); + String bName = Utils.getBucketName(bucketFile); // now create buckets int cbCode = getHttpHandler().createBucket(bfi, bName, signatureKey); @@ -303,8 +302,7 @@ public void testUploadObject() throws IOException, JSONException { for (File bucketFile : listOfIBucketInputs) { String bucketContent = Utils.readFileContentsAsString(bucketFile); assertNotNull(bucketContent); - String bucketName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, - bucketFile.getName().indexOf(".")); + String bucketName = Utils.getBucketName(bucketFile); // Get object for upload. File fileRawData = new File(Constant.RAW_DATA_PATH); File[] files = fileRawData.listFiles(); @@ -398,8 +396,7 @@ public void testUploadObjectFailed(){ Constant.CREATE_BUCKET_PATH); // Get bucket name. for (File bucketFile : listOfIBucketInputs) { - String bucketName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, - bucketFile.getName().indexOf(".")); + String bucketName = Utils.getBucketName(bucketFile); int code = getHttpHandler().uploadObject(signatureKey, bucketName, "", mFilePath); System.out.println("Verifying upload object in existing bucket with file name is empty"); @@ -421,8 +418,7 @@ public void testDownloadNonExistFile() throws IOException { String bucketContent = Utils.readFileContentsAsString(bucketFile); assertNotNull(bucketContent); String fileName = "download_image.jpg"; - String bucketName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, - bucketFile.getName().indexOf(".")); + String bucketName = Utils.getBucketName(bucketFile); Response response = getHttpHandler().downloadObject(signatureKey, bucketName, "23455", fileName); int code = response.code(); @@ -492,8 +488,7 @@ public void testDownloadObject() throws IOException { for (File bucketFile : listOfIBucketInputs) { String bucketContent = Utils.readFileContentsAsString(bucketFile); assertNotNull(bucketContent); - String bucketName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, - bucketFile.getName().indexOf(".")); + String bucketName = Utils.getBucketName(bucketFile); // Get object for upload. File fileRawData = new File(Constant.RAW_DATA_PATH); File[] files = fileRawData.listFiles(); @@ -681,8 +676,7 @@ public void testDeleteNonEmptyBucket() throws IOException { assertNotNull(bucketContent); // filename format is "bucket_.json", get the bucket name here - String bName = bucketFile.getName().substring(bucketFile.getName().indexOf("_") + 1, - bucketFile.getName().indexOf(".")); + String bName = Utils.getBucketName(bucketFile); // Verifying Bucket not empty Response response = getHttpHandler().deleteBucketNotEmpty(signatureKey, bName); int responseCode = response.code(); @@ -694,6 +688,177 @@ public void testDeleteNonEmptyBucket() throws IOException { } } } + + @Test + @Order(17) + @DisplayName("Test deleting non exist object") + public void testDeleteNonExistObject() throws IOException, JSONException { + // load input files for each type. + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken() + .getProject().getId()); + // create the bucket specified in each file + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + + // filename format is "bucket_.json", get the bucket name here + String bName = Utils.getBucketName(bucketFile); + String fileName = "hjdhj"; + // now delete the object + Response response = getHttpHandler().deleteObject(signatureKey, bName, fileName); + int resCode = response.code(); + String resBody = response.body().string(); + Logger.logString("Response Code: " + resCode); + Logger.logString("Response: " + resBody); + assertEquals("Delete non exist object: Response code not matched: ",resCode, 404); + boolean isUploaded = testGetListOfObjectFromBucket(bName, fileName, signatureKey); + assertFalse(isUploaded,"Object is exist"); + } + } + } + } + + @Test + @Order(18) + @DisplayName("Test deleting non exist object with bucket") + public void testDeleteNonExistObjectWithBucket() throws IOException { + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + String bName = "fhy5657"; + // now delete the object + Response response = getHttpHandler().deleteObject(signatureKey, bName, "hjdhj"); + int code = response.code(); + String body = response.body().string(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + body); + assertEquals("Delete non exist object: Response code not matched: ",code, 404); + Response listObjectResponse = getHttpHandler().getBucketObjects(bName,signatureKey); + int resCode = listObjectResponse.code(); + String resBody = listObjectResponse.body().string(); + Logger.logString("Response Code: " + resCode); + Logger.logString("Response: " + resBody); + assertEquals("Bucket name not exist: Response code not matched: ", resCode,404); + } + + @Test + @Order(19) + @DisplayName("Test deleting bucket and object") + public void testDeleteBucketAndObject() throws IOException, JSONException { + // load input files for each type + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + Constant.CREATE_BUCKET_PATH); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + // create the bucket specified in each file + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + + // filename format is "bucket_.json", get the bucket name here + String bName = Utils.getBucketName(bucketFile); + String fileName = "Screenshot_1.jpg"; + // now delete the object + Response response = getHttpHandler().deleteObject(signatureKey, bName, fileName); + int code = response.code(); + String body = response.body().string(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + body); + assertFalse(TextUtils.isEmpty(body),"Response message is empty: "); + assertEquals("Verifying object is deleted: Response code not matched: ",code, 204); + boolean isUploaded = testGetListOfObjectFromBucket(bName, fileName, signatureKey); + assertFalse(isUploaded,"Object is not Deleted"); + + // now delete the bucket + Response responseDeleteBucket = getHttpHandler().deleteBucket(signatureKey, bName); + int codeRes = responseDeleteBucket.code(); + String bodyRes = responseDeleteBucket.body().string(); + Logger.logString("Response Code: " + codeRes); + Logger.logString("Response: " + bodyRes); + assertFalse(TextUtils.isEmpty(bodyRes),"Response message is empty: "); + assertNotNull(bodyRes); + assertEquals("Delete bucket may be does not exist: ",codeRes,204); + + boolean isBucketExist = testGetListBuckets(bName, signatureKey); + assertFalse(isBucketExist, "Bucket is exist: "); + } + } + } + } + + @Test + @Order(20) + @DisplayName("Test deleting backend") + public void testDeleteBackend() throws IOException { + // load input files for each type. + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), + Constant.CREATE_BUCKET_PATH); + Gson gson = new Gson(); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + assertNotNull(content); + AddBackendInputHolder inputHolder = gson.fromJson(content, AddBackendInputHolder.class); + + // Get backend list + Response response = getHttpHandler().getBackends(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + int code = response.code(); + Logger.logString("Response Code: " + code); + assertEquals(code, 200); + String responseBody = response.body().string(); + assertFalse(TextUtils.isEmpty(responseBody),"Response message is empty: "); + BackendsInputHolder backendsInputHolder = gson.fromJson(responseBody, + BackendsInputHolder.class); + // Filter backend + List backendFilter = backendsInputHolder.getBackends().stream() + .filter(p -> !TextUtils.isEmpty(p.getName())) + .collect(Collectors.toList()); + + List backend = backendFilter.stream() + .filter(p -> p.getName().equals(inputHolder.getName())) + .collect(Collectors.toList()); + assertNotNull(backend); + + // Get backend + for (int i = 0; i < backend.size(); i++) { + Response responseDeleteBackend= getHttpHandler().getDeleteBackend(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken() + .getProject().getId(), backend.get(i).getId()); + int responseCode = responseDeleteBackend.code(); + String resp = responseDeleteBackend.body().string(); + Logger.logString("Response Code: " + responseCode); + Logger.logString("Response: " + resp); + assertEquals(responseCode, 200); + assertFalse(TextUtils.isEmpty(resp),"Response message is empty: "); + } + } + } + } } From 493f40e56c30edbd7bc129e4878529993478203d Mon Sep 17 00:00:00 2001 From: Click2cloud-Wadhai Date: Mon, 25 May 2020 17:46:53 +0530 Subject: [PATCH 5/5] Added test case for immediate migration also added base class for common methods. --- .../main/java/com/opensds/HttpHandler.java | 53 ++++ .../inputs/createmigration/DestConnInput.java | 34 +++ .../inputs/createmigration/Filter.java | 4 + .../createmigration/PlaneRequestInput.java | 94 ++++++ .../PlaneScheduleRequestInput.java | 118 ++++++++ .../createmigration/PoliciesRequestInput.java | 58 ++++ .../inputs/createmigration/Schedule.java | 34 +++ .../createmigration/SourceConnInput.java | 34 +++ .../main/java/com/opensds/utils/Constant.java | 1 + .../java/com/opensds/utils/ConstantUrl.java | 29 ++ .../main/java/com/opensds/utils/Utils.java | 12 + .../createmigration/bucket_b134561.json | 5 + .../createmigration/bucket_b134562.json | 5 + .../inputs/createmigration/ibm-cos_b1321.json | 9 + .../src/test/java/BaseTestClass.java | 239 +++++++++++++++ .../test/java/CreateBucketBackendTest.java | 273 ++---------------- .../src/test/java/MigrationTests.java | 174 +++++++++++ 17 files changed, 924 insertions(+), 252 deletions(-) create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/DestConnInput.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/Filter.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PlaneRequestInput.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PlaneScheduleRequestInput.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PoliciesRequestInput.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/Schedule.java create mode 100644 testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/SourceConnInput.java create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/createmigration/bucket_b134561.json create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/createmigration/bucket_b134562.json create mode 100644 testhelper/open-sds-junit/src/main/resources/inputs/createmigration/ibm-cos_b1321.json create mode 100644 testhelper/open-sds-junit/src/test/java/BaseTestClass.java create mode 100644 testhelper/open-sds-junit/src/test/java/MigrationTests.java diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java b/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java index 4607c2ed6..5022219b3 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/HttpHandler.java @@ -21,6 +21,7 @@ import okio.Okio; import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -268,4 +269,56 @@ public Response deleteBucket(SignatureKey signatureKey, String bucketName) { String url = ConstantUrl.getInstance().getDeleteBucketUrl(bucketName); return deleteCallWithV4Sign(client, url, signatureKey); } + + public Response createPlans(String xAuthToken, String body, String tenantId) { + Response response = null; + RequestBody requestBody = RequestBody.create(body, + MediaType.parse(CONTENT_TYPE_JSON_CHARSET)); + String url = ConstantUrl.getInstance().getCreatePlansUrl(tenantId); + Logger.logString("URL: "+url); + Map headersMap = new HashMap<>(); + headersMap.put(X_AUTH_TOKEN, xAuthToken); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + Headers headers = Headers.of(headersMap); + try { + response = postCall(client, url, requestBody, headers); + } catch (IOException e) { + e.printStackTrace(); + } + return response; + } + + public Response runPlans(String xAuthToken, String id, String tenantId) { + Response response = null; + RequestBody requestBody = RequestBody.create("", + MediaType.parse(CONTENT_TYPE_JSON_CHARSET)); + String url = ConstantUrl.getInstance().getRunPlanUrl(tenantId, id); + Logger.logString("URL: "+url); + Map headersMap = new HashMap<>(); + headersMap.put(X_AUTH_TOKEN, xAuthToken); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + Headers headers = Headers.of(headersMap); + try { + response = postCall(client, url, requestBody, headers); + } catch (IOException e) { + e.printStackTrace(); + } + return response; + } + + public Response getJob(String xAuthToken, String jobId, String tenantId) { + Response response = null; + String url = ConstantUrl.getInstance().getJobUrl(tenantId, jobId); + Logger.logString("URL: "+url); + Map headersMap = new HashMap<>(); + headersMap.put(X_AUTH_TOKEN, xAuthToken); + headersMap.put(CONTENT_TYPE, CONTENT_TYPE_JSON); + Headers headers = Headers.of(headersMap); + try { + return getCall(client, url, headers); + } catch (IOException e) { + e.printStackTrace(); + } + return response; + } } \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/DestConnInput.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/DestConnInput.java new file mode 100644 index 000000000..1b54f6cba --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/DestConnInput.java @@ -0,0 +1,34 @@ +package com.opensds.jsonmodels.inputs.createmigration; + +public class DestConnInput +{ + private String bucketName; + + private String storType; + + public String getBucketName () + { + return bucketName; + } + + public void setBucketName (String bucketName) + { + this.bucketName = bucketName; + } + + public String getStorType () + { + return storType; + } + + public void setStorType (String storType) + { + this.storType = storType; + } + + @Override + public String toString() + { + return "ClassPojo [bucketName = "+bucketName+", storType = "+storType+"]"; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/Filter.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/Filter.java new file mode 100644 index 000000000..47bd5bbec --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/Filter.java @@ -0,0 +1,4 @@ +package com.opensds.jsonmodels.inputs.createmigration; + +public class Filter { +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PlaneRequestInput.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PlaneRequestInput.java new file mode 100644 index 000000000..97b8075b4 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PlaneRequestInput.java @@ -0,0 +1,94 @@ +package com.opensds.jsonmodels.inputs.createmigration; + +public class PlaneRequestInput +{ + private Filter filter; + + private boolean remainSource; + + private DestConnInput destConn; + + private String name; + + private String description; + + private String type; + + private SourceConnInput sourceConn; + + public Filter getFilter () + { + return filter; + } + + public void setFilter (Filter filter) + { + this.filter = filter; + } + + public boolean getRemainSource () + { + return remainSource; + } + + public void setRemainSource (boolean remainSource) + { + this.remainSource = remainSource; + } + + public DestConnInput getDestConn () + { + return destConn; + } + + public void setDestConn (DestConnInput destConn) + { + this.destConn = destConn; + } + + public String getName () + { + return name; + } + + public void setName (String name) + { + this.name = name; + } + + public String getDescription () + { + return description; + } + + public void setDescription (String description) + { + this.description = description; + } + + public String getType () + { + return type; + } + + public void setType (String type) + { + this.type = type; + } + + public SourceConnInput getSourceConn () + { + return sourceConn; + } + + public void setSourceConn (SourceConnInput sourceConn) + { + this.sourceConn = sourceConn; + } + + @Override + public String toString() + { + return "ClassPojo [filter = "+filter+", remainSource = "+remainSource+", destConn = "+destConn+", name = "+name+", description = "+description+", type = "+type+", sourceConn = "+sourceConn+"]"; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PlaneScheduleRequestInput.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PlaneScheduleRequestInput.java new file mode 100644 index 000000000..581bf88e1 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PlaneScheduleRequestInput.java @@ -0,0 +1,118 @@ +package com.opensds.jsonmodels.inputs.createmigration; + +public class PlaneScheduleRequestInput +{ + private Filter filter; + + private boolean remainSource; + + private String policyId; + + private DestConnInput destConn; + + private String name; + + private String description; + + private String type; + + private SourceConnInput sourceConn; + + private boolean policyEnabled; + + public Filter getFilter () + { + return filter; + } + + public void setFilter (Filter filter) + { + this.filter = filter; + } + + public boolean getRemainSource () + { + return remainSource; + } + + public void setRemainSource (boolean remainSource) + { + this.remainSource = remainSource; + } + + public String getPolicyId () + { + return policyId; + } + + public void setPolicyId (String policyId) + { + this.policyId = policyId; + } + + public DestConnInput getDestConn () + { + return destConn; + } + + public void setDestConn (DestConnInput destConn) + { + this.destConn = destConn; + } + + public String getName () + { + return name; + } + + public void setName (String name) + { + this.name = name; + } + + public String getDescription () + { + return description; + } + + public void setDescription (String description) + { + this.description = description; + } + + public String getType () + { + return type; + } + + public void setType (String type) + { + this.type = type; + } + + public SourceConnInput getSourceConn () + { + return sourceConn; + } + + public void setSourceConn (SourceConnInput sourceConn) + { + this.sourceConn = sourceConn; + } + + public boolean getPolicyEnabled () + { + return policyEnabled; + } + + public void setPolicyEnabled (boolean policyEnabled) + { + this.policyEnabled = policyEnabled; + } + + @Override + public String toString() + { + return "ClassPojo [filter = "+filter+", remainSource = "+remainSource+", policyId = "+policyId+", destConn = "+destConn+", name = "+name+", description = "+description+", type = "+type+", sourceConn = "+sourceConn+", policyEnabled = "+policyEnabled+"]"; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PoliciesRequestInput.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PoliciesRequestInput.java new file mode 100644 index 000000000..8cfdf4885 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/PoliciesRequestInput.java @@ -0,0 +1,58 @@ +package com.opensds.jsonmodels.inputs.createmigration; + +public class PoliciesRequestInput +{ + private Schedule schedule; + + private String name; + + private String description; + + private String tenant; + + public Schedule getSchedule () + { + return schedule; + } + + public void setSchedule (Schedule schedule) + { + this.schedule = schedule; + } + + public String getName () + { + return name; + } + + public void setName (String name) + { + this.name = name; + } + + public String getDescription () + { + return description; + } + + public void setDescription (String description) + { + this.description = description; + } + + public String getTenant () + { + return tenant; + } + + public void setTenant (String tenant) + { + this.tenant = tenant; + } + + @Override + public String toString() + { + return "ClassPojo [schedule = "+schedule+", name = "+name+", description = "+description+", tenant = "+tenant+"]"; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/Schedule.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/Schedule.java new file mode 100644 index 000000000..a8dec3433 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/Schedule.java @@ -0,0 +1,34 @@ +package com.opensds.jsonmodels.inputs.createmigration; + +public class Schedule +{ + private String type; + + private String tiggerProperties; + + public String getType () + { + return type; + } + + public void setType (String type) + { + this.type = type; + } + + public String getTiggerProperties () + { + return tiggerProperties; + } + + public void setTiggerProperties (String tiggerProperties) + { + this.tiggerProperties = tiggerProperties; + } + + @Override + public String toString() + { + return "ClassPojo [type = "+type+", tiggerProperties = "+tiggerProperties+"]"; + } +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/SourceConnInput.java b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/SourceConnInput.java new file mode 100644 index 000000000..ccd893d22 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/jsonmodels/inputs/createmigration/SourceConnInput.java @@ -0,0 +1,34 @@ +package com.opensds.jsonmodels.inputs.createmigration; + +public class SourceConnInput +{ + private String bucketName; + + private String storType; + + public String getBucketName () + { + return bucketName; + } + + public void setBucketName (String bucketName) + { + this.bucketName = bucketName; + } + + public String getStorType () + { + return storType; + } + + public void setStorType (String storType) + { + this.storType = storType; + } + + @Override + public String toString() + { + return "ClassPojo [bucketName = "+bucketName+", storType = "+storType+"]"; + } +} diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java index f28ba7341..7b5e1b1a8 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Constant.java @@ -6,4 +6,5 @@ public class Constant { public static final String EMPTY_FIELD_PATH = PATH+"emptyfield/"; public static final String RAW_DATA_PATH = PATH+"rawdata"; public static final String DOWNLOAD_FILES_PATH = PATH+"download"; + public static final String CREATE_MIGRATION_PATH = PATH+"createmigration"; } \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java index 6f1ea5746..12ea23a51 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/ConstantUrl.java @@ -167,4 +167,33 @@ public String getDeleteBucketUrl(String bucketName) { public String getDeleteObjectUrl(String bucketName, String objectName) { return URL+PORT+"/"+bucketName+"/"+objectName; } + + /** + * Create plan. + * + * @param tenantId tenant id + */ + public String getCreatePlansUrl(String tenantId) { + return URL +PORT_TENANT_ID+"/"+tenantId+"/plans"; + } + + /** + * Run plan. + * + * @param tenantId tenant id + * @param id id + */ + public String getRunPlanUrl(String tenantId, String id) { + return URL +PORT_TENANT_ID+"/"+tenantId+"/plans/"+id+"/run"; + } + + /** + * Get job + * + * @param tenantId tenant id + * @param jobId job id + */ + public String getJobUrl(String tenantId, String jobId) { + return URL +PORT_TENANT_ID+"/"+tenantId+"/jobs/"+jobId; + } } diff --git a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Utils.java b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Utils.java index 84da66365..8c97df9a8 100644 --- a/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Utils.java +++ b/testhelper/open-sds-junit/src/main/java/com/opensds/utils/Utils.java @@ -98,4 +98,16 @@ public static String getHost(String url){ } return host; } + + /** + * Generate random name. + * + * @param name any name + * @return name + */ + public static String getRandomName(String name){ + Random rand = new Random(); + int randInt = rand.nextInt(10000); + return name+randInt; + } } diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/bucket_b134561.json b/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/bucket_b134561.json new file mode 100644 index 000000000..1d0c5aec6 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/bucket_b134561.json @@ -0,0 +1,5 @@ +{ + "xmlRequestFalse":" falsefalsestring", + "xmlRequestTrue":" truefalsestring", + "xmlPayload":"ibm-back123400" +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/bucket_b134562.json b/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/bucket_b134562.json new file mode 100644 index 000000000..1d0c5aec6 --- /dev/null +++ b/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/bucket_b134562.json @@ -0,0 +1,5 @@ +{ + "xmlRequestFalse":" falsefalsestring", + "xmlRequestTrue":" truefalsestring", + "xmlPayload":"ibm-back123400" +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/ibm-cos_b1321.json b/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/ibm-cos_b1321.json new file mode 100644 index 000000000..2c16d138b --- /dev/null +++ b/testhelper/open-sds-junit/src/main/resources/inputs/createmigration/ibm-cos_b1321.json @@ -0,0 +1,9 @@ +{ + "name": "************", + "type": "************", + "region": "************", + "endpoint": "************", + "bucketName": "************", + "security": "************", + "access": "*************" +} \ No newline at end of file diff --git a/testhelper/open-sds-junit/src/test/java/BaseTestClass.java b/testhelper/open-sds-junit/src/test/java/BaseTestClass.java new file mode 100644 index 000000000..9edfe0681 --- /dev/null +++ b/testhelper/open-sds-junit/src/test/java/BaseTestClass.java @@ -0,0 +1,239 @@ +import com.google.gson.Gson; +import com.opensds.HttpHandler; +import com.opensds.jsonmodels.akskresponses.SignatureKey; +import com.opensds.jsonmodels.authtokensresponses.AuthTokenHolder; +import com.opensds.jsonmodels.inputs.addbackend.AddBackendInputHolder; +import com.opensds.jsonmodels.inputs.createbucket.CreateBucketFileInput; +import com.opensds.jsonmodels.tokensresponses.TokenHolder; +import com.opensds.jsonmodels.typesresponse.Type; +import com.opensds.jsonmodels.typesresponse.TypesHolder; +import com.opensds.utils.Constant; +import com.opensds.utils.Logger; +import com.opensds.utils.TextUtils; +import com.opensds.utils.Utils; +import okhttp3.Response; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.XML; +import org.junit.jupiter.api.Assertions; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static com.opensds.utils.Constant.*; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.*; + +public class BaseTestClass { + public static AuthTokenHolder getAuthTokenHolder() { + return mAuthTokenHolder; + } + + public TypesHolder getTypesHolder() { + return mTypesHolder; + } + + public static HttpHandler getHttpHandler() { + return mHttpHandler; + } + + private static AuthTokenHolder mAuthTokenHolder = null; + private static TypesHolder mTypesHolder = null; + private static HttpHandler mHttpHandler = new HttpHandler(); + + @org.junit.jupiter.api.BeforeAll + static void setUp() { + TokenHolder tokenHolder = getHttpHandler().loginAndGetToken(); + mAuthTokenHolder = getHttpHandler().getAuthToken(tokenHolder.getResponseHeaderSubjectToken()); + mTypesHolder = getHttpHandler().getTypes(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + } + + public void testCreateBucketAndBackend(String path) throws IOException, JSONException { + // load input files for each type and create the backend + for (Type t : getTypesHolder().getTypes()) { + List listOfIInputsForType = + Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), path); + Gson gson = new Gson(); + // add the backend specified in each file + for (File file : listOfIInputsForType) { + String content = Utils.readFileContentsAsString(file); + Assertions.assertNotNull(content); + + AddBackendInputHolder inputHolder = gson.fromJson(content, AddBackendInputHolder.class); + int code = getHttpHandler().addBackend(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId(), + inputHolder); + assertEquals(code, 200); + + // backend added, now create buckets + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", path); + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + // create the bucket specified in each file + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + Assertions.assertNotNull(bucketContent); + + CreateBucketFileInput bfi = gson.fromJson(bucketContent, CreateBucketFileInput.class); + + // filename format is "bucket_.json", get the bucket name here + String bName = Utils.getBucketName(bucketFile); + + // now create buckets + int cbCode = getHttpHandler().createBucket(bfi, bName, signatureKey); + assertEquals(cbCode, 200); + boolean isBucketExist = testGetListBuckets(bName, signatureKey); + assertTrue(isBucketExist, "Bucket is not exist: "); + } + } + } + } + + public void testDownloadObject(String path, String objectName) throws IOException { + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", path); + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + String bucketName = Utils.getBucketName(bucketFile); + // Get object for upload. + File fileRawData = new File(RAW_DATA_PATH); + File[] files = fileRawData.listFiles(); + String mFileName = null; + for (File fileName : files) { + mFileName = fileName.getName(); + } + String fileName = bucketName+objectName; + File filePath = new File(DOWNLOAD_FILES_PATH); + File downloadedFile = new File(DOWNLOAD_FILES_PATH, fileName); + if (filePath.exists()) { + if (downloadedFile.exists()) { + boolean isDownloadedFileDeleted = downloadedFile.delete(); + assertTrue(isDownloadedFileDeleted, "Image deleting is failed"); + } else { + assertFalse(downloadedFile.exists()); + } + } else { + filePath.mkdirs(); + } + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + Response response = getHttpHandler().downloadObject(signatureKey, + bucketName, mFileName, fileName); + int code = response.code(); + String body = response.body().string(); + Logger.logString("Response Code: " + code); + Logger.logString("Response: " + body); + assertEquals("Downloading failed", code, 200); + assertTrue(downloadedFile.isFile(), "Downloaded Image is not available"); + } + } + + public void testUploadObject(String path) throws IOException, JSONException { + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", path); + // Get bucket name. + for (File bucketFile : listOfIBucketInputs) { + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + String bucketName = Utils.getBucketName(bucketFile); + // Get object for upload. + File fileRawData = new File(Constant.RAW_DATA_PATH); + File[] files = fileRawData.listFiles(); + String mFileName; + File mFilePath; + for (File fileName : files) { + mFileName = fileName.getName(); + mFilePath = fileName; + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), + getAuthTokenHolder().getToken().getProject().getId()); + int cbCode = getHttpHandler().uploadObject(signatureKey, + bucketName, mFileName, mFilePath); + assertEquals("Uploaded object failed", cbCode, 200); + //Verifying object is uploaded in bucket. + boolean isUploaded = testGetListOfObjectFromBucket(bucketName, mFileName, signatureKey); + assertTrue(isUploaded,"Object is not uploaded"); + } + } + } + + /** + * Get bucket list. + * + * @param bName Bucket name + * @param signatureKey Signature key object + * @return boolean If bucket is exist return true else false + * @throws JSONException Json exception + * @throws IOException Io exception + */ + protected boolean testGetListBuckets(String bName, SignatureKey signatureKey) + throws JSONException, IOException { + Response listBucketResponse = getHttpHandler().getBuckets(signatureKey); + int resCode = listBucketResponse.code(); + String responseBody = listBucketResponse.body().string(); + Logger.logString("Response Code: " + resCode); + Logger.logString("Response: " + responseBody); + JSONObject jsonObject = XML.toJSONObject(responseBody); + JSONArray jsonObjectBucketList = jsonObject.getJSONObject("ListAllMyBucketsResult") + .getJSONObject("Buckets").getJSONArray("Bucket"); + boolean isBucketExist = false; + for (int i = 0; i < jsonObjectBucketList.length(); i++) { + String bucketName = jsonObjectBucketList.getJSONObject(i).get("Name").toString(); + if (!TextUtils.isEmpty(bucketName)) { + if (bucketName.equals(bName)) { + isBucketExist = true; + } + } + } + return isBucketExist; + } + + /** + * Get List of object from bucket + * + * @param bucketName bucket name + * @param fileName file name + * @param signatureKey signature key object + * @return boolean object is upload then true else false + * @throws JSONException json exception + * @throws IOException io exception + */ + protected boolean testGetListOfObjectFromBucket(String bucketName, String fileName, + SignatureKey signatureKey) + throws JSONException, IOException { + Response listObjectResponse = getHttpHandler().getBucketObjects(bucketName, signatureKey); + int resCode = listObjectResponse.code(); + String resBody = listObjectResponse.body().string(); + Logger.logString("Response Code: " + resCode); + Logger.logString("Response: " + resBody); + assertEquals("Get list of object failed", resCode, 200); + JSONObject jsonObject = XML.toJSONObject(resBody); + JSONObject jsonObjectListBucket = jsonObject.getJSONObject("ListBucketResult"); + boolean isUploaded = false; + if (jsonObjectListBucket.has("Contents")) { + if (jsonObjectListBucket.get("Contents") instanceof JSONArray) { + JSONArray objects = jsonObjectListBucket.getJSONArray("Contents"); + for (int i = 0; i < objects.length(); i++) { + if (!TextUtils.isEmpty(objects.getJSONObject(i).get("Key").toString())) { + if (objects.getJSONObject(i).get("Key").toString().equals(fileName)) { + isUploaded = true; + } + } + } + } else { + if (!TextUtils.isEmpty(jsonObjectListBucket.getJSONObject("Contents") + .get("Key").toString())) { + if (jsonObjectListBucket.getJSONObject("Contents").get("Key").toString() + .equals(fileName)) { + isUploaded = true; + } + } + } + } + return isUploaded; + } +} diff --git a/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java b/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java index f40b28a59..79073160d 100644 --- a/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java +++ b/testhelper/open-sds-junit/src/test/java/CreateBucketBackendTest.java @@ -1,23 +1,16 @@ import com.google.gson.Gson; -import com.opensds.HttpHandler; import com.opensds.jsonmodels.akskresponses.SignatureKey; -import com.opensds.jsonmodels.authtokensresponses.AuthTokenHolder; import com.opensds.jsonmodels.inputs.addbackend.AddBackendInputHolder; import com.opensds.jsonmodels.inputs.addbackend.Backends; import com.opensds.jsonmodels.inputs.addbackend.BackendsInputHolder; import com.opensds.jsonmodels.inputs.createbucket.CreateBucketFileInput; -import com.opensds.jsonmodels.tokensresponses.TokenHolder; import com.opensds.jsonmodels.typesresponse.Type; -import com.opensds.jsonmodels.typesresponse.TypesHolder; import com.opensds.utils.Constant; import com.opensds.utils.Logger; import com.opensds.utils.TextUtils; import com.opensds.utils.Utils; import okhttp3.Response; -import org.json.JSONArray; import org.json.JSONException; -import org.json.JSONObject; -import org.json.XML; import org.junit.jupiter.api.*; import java.io.File; @@ -25,6 +18,7 @@ import java.util.List; import java.util.stream.Collectors; +import static com.opensds.utils.Constant.CREATE_BUCKET_PATH; import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.*; @@ -32,108 +26,13 @@ // http://pojo.sodhanalibrary.com/ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class CreateBucketBackendTest { - - public static AuthTokenHolder getAuthTokenHolder() { - return mAuthTokenHolder; - } - - public TypesHolder getTypesHolder() { - return mTypesHolder; - } - - public static HttpHandler getHttpHandler() { - return mHttpHandler; - } - - private static AuthTokenHolder mAuthTokenHolder = null; - private static TypesHolder mTypesHolder = null; - private static HttpHandler mHttpHandler = new HttpHandler(); - - @org.junit.jupiter.api.BeforeAll - static void setUp() { - TokenHolder tokenHolder = getHttpHandler().loginAndGetToken(); - mAuthTokenHolder = getHttpHandler().getAuthToken(tokenHolder.getResponseHeaderSubjectToken()); - mTypesHolder = getHttpHandler().getTypes(getAuthTokenHolder().getResponseHeaderSubjectToken(), - getAuthTokenHolder().getToken().getProject().getId()); - } +class CreateBucketBackendTest extends BaseTestClass { @Test @Order(1) @DisplayName("Test creating bucket and backend on OPENSDS") public void testCreateBucketAndBackend() throws IOException, JSONException { - // load input files for each type and create the backend - for (Type t : getTypesHolder().getTypes()) { - List listOfIInputsForType = - Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); - Gson gson = new Gson(); - // add the backend specified in each file - for (File file : listOfIInputsForType) { - String content = Utils.readFileContentsAsString(file); - assertNotNull(content); - - AddBackendInputHolder inputHolder = gson.fromJson(content, AddBackendInputHolder.class); - int code = getHttpHandler().addBackend(getAuthTokenHolder().getResponseHeaderSubjectToken(), - getAuthTokenHolder().getToken().getProject().getId(), - inputHolder); - assertEquals(code, 200); - - // backend added, now create buckets - List listOfIBucketInputs = - Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); - SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), - getAuthTokenHolder().getToken().getProject().getId()); - // create the bucket specified in each file - for (File bucketFile : listOfIBucketInputs) { - String bucketContent = Utils.readFileContentsAsString(bucketFile); - assertNotNull(bucketContent); - - CreateBucketFileInput bfi = gson.fromJson(bucketContent, CreateBucketFileInput.class); - - // filename format is "bucket_.json", get the bucket name here - String bName = Utils.getBucketName(bucketFile); - - // now create buckets - int cbCode = getHttpHandler().createBucket(bfi, bName, signatureKey); - assertEquals(cbCode, 200); - boolean isBucketExist = testGetListBuckets(bName, signatureKey); - assertTrue(isBucketExist, "Bucket is not exist: "); - } - } - } - } - - /** - * Get bucket list. - * - * @param bName Bucket name - * @param signatureKey Signature key object - * @return boolean If bucket is exist return true else false - * @throws JSONException Json exception - * @throws IOException Io exception - */ - private boolean testGetListBuckets(String bName, SignatureKey signatureKey) - throws JSONException, IOException { - Response listBucketResponse = getHttpHandler().getBuckets(signatureKey); - int resCode = listBucketResponse.code(); - String responseBody = listBucketResponse.body().string(); - Logger.logString("Response Code: " + resCode); - Logger.logString("Response: " + responseBody); - JSONObject jsonObject = XML.toJSONObject(responseBody); - JSONArray jsonObjectBucketList = jsonObject.getJSONObject("ListAllMyBucketsResult") - .getJSONObject("Buckets").getJSONArray("Bucket"); - boolean isBucketExist = false; - for (int i = 0; i < jsonObjectBucketList.length(); i++) { - String bucketName = jsonObjectBucketList.getJSONObject(i).get("Name").toString(); - if (!TextUtils.isEmpty(bucketName)) { - if (bucketName.equals(bName)) { - isBucketExist = true; - } - } - } - return isBucketExist; + testCreateBucketAndBackend(CREATE_BUCKET_PATH); } @Test @@ -144,7 +43,7 @@ public void testCreateBucketUsingCapsName() { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); Gson gson = new Gson(); // add the backend specified in each file for (File file : listOfIInputsForType) { @@ -154,7 +53,7 @@ public void testCreateBucketUsingCapsName() { // backend added, now create buckets List listOfIBucketInputs = Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken().getProject().getId()); // create the bucket specified in each file @@ -181,7 +80,7 @@ public void testReCreateBackend() { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); Gson gson = new Gson(); // Re-create backend specified in each file for (File file : listOfIInputsForType) { @@ -205,7 +104,7 @@ public void testReCreateBucket() { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); Gson gson = new Gson(); // Re-create backend specified in each file for (File file : listOfIInputsForType) { @@ -213,7 +112,7 @@ public void testReCreateBucket() { assertNotNull(content); List listOfIBucketInputs = Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken().getProject().getId()); // create the bucket specified in each file @@ -287,89 +186,7 @@ public void testRequestBodyWithEmptyFieldBackend() { @Order(7) @DisplayName("Test uploading object in a bucket") public void testUploadObject() throws IOException, JSONException { - // load input files for each type - for (Type t : getTypesHolder().getTypes()) { - List listOfIInputsForType = - Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); - for (File file : listOfIInputsForType) { - String content = Utils.readFileContentsAsString(file); - assertNotNull(content); - List listOfIBucketInputs = - Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); - // Get bucket name. - for (File bucketFile : listOfIBucketInputs) { - String bucketContent = Utils.readFileContentsAsString(bucketFile); - assertNotNull(bucketContent); - String bucketName = Utils.getBucketName(bucketFile); - // Get object for upload. - File fileRawData = new File(Constant.RAW_DATA_PATH); - File[] files = fileRawData.listFiles(); - String mFileName = null; - File mFilePath = null; - for (File fileName : files) { - mFileName = fileName.getName(); - mFilePath = fileName; - - SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), - getAuthTokenHolder().getToken().getProject().getId()); - int cbCode = getHttpHandler().uploadObject(signatureKey, - bucketName, mFileName, mFilePath); - assertEquals("Uploaded object failed", cbCode, 200); - - //Verifying object is uploaded in bucket. - boolean isUploaded = testGetListOfObjectFromBucket(bucketName, mFileName, signatureKey); - assertTrue(isUploaded,"Object is not uploaded"); - } - } - } - } - } - - /** - * Get List of object from bucket - * - * @param bucketName bucket name - * @param fileName file name - * @param signatureKey signature key object - * @return boolean object is upload then true else false - * @throws JSONException json exception - * @throws IOException io exception - */ - private boolean testGetListOfObjectFromBucket(String bucketName, String fileName, - SignatureKey signatureKey) - throws JSONException, IOException { - Response listObjectResponse = getHttpHandler().getBucketObjects(bucketName, signatureKey); - int resCode = listObjectResponse.code(); - String resBody = listObjectResponse.body().string(); - Logger.logString("Response Code: " + resCode); - Logger.logString("Response: " + resBody); - assertEquals("Get list of object failed", resCode, 200); - JSONObject jsonObject = XML.toJSONObject(resBody); - JSONObject jsonObjectListBucket = jsonObject.getJSONObject("ListBucketResult"); - boolean isUploaded = false; - if (jsonObjectListBucket.has("Contents")) { - if (jsonObjectListBucket.get("Contents") instanceof JSONArray) { - JSONArray objects = jsonObjectListBucket.getJSONArray("Contents"); - for (int i = 0; i < objects.length(); i++) { - if (!TextUtils.isEmpty(objects.getJSONObject(i).get("Key").toString())) { - if (objects.getJSONObject(i).get("Key").toString().equals(fileName)) { - isUploaded = true; - } - } - } - } else { - if (!TextUtils.isEmpty(jsonObjectListBucket.getJSONObject("Contents") - .get("Key").toString())) { - if (jsonObjectListBucket.getJSONObject("Contents").get("Key").toString() - .equals(fileName)) { - isUploaded = true; - } - } - } - } - return isUploaded; + testUploadObject(CREATE_BUCKET_PATH); } @Test @@ -393,7 +210,7 @@ public void testUploadObjectFailed(){ List listOfIBucketInputs = Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); // Get bucket name. for (File bucketFile : listOfIBucketInputs) { String bucketName = Utils.getBucketName(bucketFile); @@ -411,7 +228,7 @@ public void testUploadObjectFailed(){ public void testDownloadNonExistFile() throws IOException { List listOfIBucketInputs = Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken().getProject().getId()); for (File bucketFile : listOfIBucketInputs) { @@ -473,55 +290,7 @@ public void testDownloadNonExistBucketAndFile() throws IOException { @Order(12) @DisplayName("Test downloading object in a folder") public void testDownloadObject() throws IOException { - // load input files for each type - for (Type t : getTypesHolder().getTypes()) { - List listOfIInputsForType = - Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); - for (File file : listOfIInputsForType) { - String content = Utils.readFileContentsAsString(file); - assertNotNull(content); - // backend added, now create buckets - List listOfIBucketInputs = - Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); - for (File bucketFile : listOfIBucketInputs) { - String bucketContent = Utils.readFileContentsAsString(bucketFile); - assertNotNull(bucketContent); - String bucketName = Utils.getBucketName(bucketFile); - // Get object for upload. - File fileRawData = new File(Constant.RAW_DATA_PATH); - File[] files = fileRawData.listFiles(); - String mFileName = null; - for (File fileName : files) { - mFileName = fileName.getName(); - } - String fileName = "download_image.jpg"; - File filePath = new File(Constant.DOWNLOAD_FILES_PATH); - File downloadedFile = new File(Constant.DOWNLOAD_FILES_PATH, fileName); - if (filePath.exists()) { - if (downloadedFile.exists()) { - boolean isDownloadedFileDeleted = downloadedFile.delete(); - assertTrue(isDownloadedFileDeleted, "Image deleting is failed"); - } else { - assertFalse(downloadedFile.exists()); - } - } else { - filePath.mkdirs(); - } - SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), - getAuthTokenHolder().getToken().getProject().getId()); - Response response = getHttpHandler().downloadObject(signatureKey, - bucketName, mFileName, fileName); - int code = response.code(); - String body = response.body().string(); - Logger.logString("Response Code: " + code); - Logger.logString("Response: " + body); - assertEquals("Downloading failed", code, 200); - assertTrue(downloadedFile.isFile(), "Downloaded Image is not available"); - } - } - } + testDownloadObject(CREATE_BUCKET_PATH,"download_image.jpg"); } @Test @@ -532,7 +301,7 @@ public void testAddBackendGetBackends() throws IOException { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); Gson gson = new Gson(); // add the backend specified in each file for (File file : listOfIInputsForType) { @@ -597,7 +366,7 @@ public void testDeleteNonEmptyBackend() throws IOException { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); Gson gson = new Gson(); // add the backend specified in each file for (File file : listOfIInputsForType) { @@ -660,7 +429,7 @@ public void testDeleteNonEmptyBucket() throws IOException { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); // add the backend specified in each file for (File file : listOfIInputsForType) { String content = Utils.readFileContentsAsString(file); @@ -669,7 +438,7 @@ public void testDeleteNonEmptyBucket() throws IOException { getAuthTokenHolder().getToken().getProject().getId()); List listOfIBucketInputs = Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); // create the bucket specified in each file for (File bucketFile : listOfIBucketInputs) { String bucketContent = Utils.readFileContentsAsString(bucketFile); @@ -697,7 +466,7 @@ public void testDeleteNonExistObject() throws IOException, JSONException { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); // add the backend specified in each file for (File file : listOfIInputsForType) { String content = Utils.readFileContentsAsString(file); @@ -705,7 +474,7 @@ public void testDeleteNonExistObject() throws IOException, JSONException { List listOfIBucketInputs = Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder() .getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken() .getProject().getId()); @@ -761,7 +530,7 @@ public void testDeleteBucketAndObject() throws IOException, JSONException { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); // add the backend specified in each file for (File file : listOfIInputsForType) { String content = Utils.readFileContentsAsString(file); @@ -769,7 +538,7 @@ public void testDeleteBucketAndObject() throws IOException, JSONException { List listOfIBucketInputs = Utils.listFilesMatchingBeginsWithPatternInPath("bucket", - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder().getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken().getProject().getId()); // create the bucket specified in each file @@ -816,7 +585,7 @@ public void testDeleteBackend() throws IOException { for (Type t : getTypesHolder().getTypes()) { List listOfIInputsForType = Utils.listFilesMatchingBeginsWithPatternInPath(t.getName(), - Constant.CREATE_BUCKET_PATH); + CREATE_BUCKET_PATH); Gson gson = new Gson(); // add the backend specified in each file for (File file : listOfIInputsForType) { diff --git a/testhelper/open-sds-junit/src/test/java/MigrationTests.java b/testhelper/open-sds-junit/src/test/java/MigrationTests.java new file mode 100644 index 000000000..3eba8e3ca --- /dev/null +++ b/testhelper/open-sds-junit/src/test/java/MigrationTests.java @@ -0,0 +1,174 @@ +import com.google.gson.Gson; +import com.opensds.jsonmodels.akskresponses.SignatureKey; +import com.opensds.jsonmodels.inputs.createmigration.DestConnInput; +import com.opensds.jsonmodels.inputs.createmigration.Filter; +import com.opensds.jsonmodels.inputs.createmigration.PlaneRequestInput; +import com.opensds.jsonmodels.inputs.createmigration.SourceConnInput; +import com.opensds.utils.Logger; +import com.opensds.utils.Utils; +import okhttp3.Response; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.jupiter.api.*; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static com.opensds.utils.Constant.*; +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.*; + +//how to get POJO from any response JSON, use this site +//http://pojo.sodhanalibrary.com/ +//UTC time conversion +// https://savvytime.com/converter/utc-to-ist +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class MigrationTests extends BaseTestClass { + + @Test + @Order(1) + @DisplayName("Test creating bucket and backend on OPENSDS") + public void testCreateBucketAndBackend() throws IOException, JSONException { + testCreateBucketAndBackend(CREATE_MIGRATION_PATH); + } + + @Test + @Order(2) + @DisplayName("Test uploading object in a 1st bucket") + public void testUploadObject() throws IOException, JSONException { + List listOfIBucketInputs = Utils.listFilesMatchingBeginsWithPatternInPath("bucket", + CREATE_MIGRATION_PATH); + assertNotNull(listOfIBucketInputs); + File bucketFile = listOfIBucketInputs.get(0); + Logger.logObject(bucketFile); + // Get bucket name. + String bucketContent = Utils.readFileContentsAsString(bucketFile); + assertNotNull(bucketContent); + String bucketName =Utils.getBucketName(bucketFile); + // Get object for upload. + File fileRawData = new File(RAW_DATA_PATH); + File[] files = fileRawData.listFiles(); + String mFileName; + File mFilePath; + for (File fileName : files) { + mFileName = fileName.getName(); + mFilePath = fileName; + + SignatureKey signatureKey = getHttpHandler().getAkSkList(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), getAuthTokenHolder().getToken() + .getProject().getId()); + int cbCode = getHttpHandler().uploadObject(signatureKey, + bucketName, mFileName, mFilePath); + assertEquals("Uploaded object failed", cbCode, 200); + + //Verifying object is uploaded in bucket. + boolean isUploaded = testGetListOfObjectFromBucket(bucketName, mFileName, signatureKey); + assertTrue(isUploaded,"Object is not uploaded"); + } + } + + @Test + @Order(3) + @DisplayName("Test creating plan with immediately") + public void testCreatePlan() throws IOException, JSONException { + Gson gson = new Gson(); + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", CREATE_MIGRATION_PATH); + assertNotNull(listOfIBucketInputs); + SourceConnInput sourceConnInput = new SourceConnInput(); + sourceConnInput.setBucketName(Utils.getBucketName(listOfIBucketInputs.get(0))); + sourceConnInput.setStorType("opensds-obj"); + DestConnInput destConnInput = new DestConnInput(); + destConnInput.setBucketName(Utils.getBucketName(listOfIBucketInputs.get(1))); + destConnInput.setStorType("opensds-obj"); + Filter filter = new Filter(); + PlaneRequestInput planeRequestInput = new PlaneRequestInput(); + planeRequestInput.setName(listOfIBucketInputs.get(0).getName()+"-Plan"); + planeRequestInput.setDescription("for test"); + planeRequestInput.setType("migration"); + planeRequestInput.setSourceConn(sourceConnInput); + planeRequestInput.setDestConn(destConnInput); + planeRequestInput.setFilter(filter); + planeRequestInput.setRemainSource(true); + String json = gson.toJson(planeRequestInput); + Logger.logString("Request Json: "+json); + + Response response = getHttpHandler().createPlans(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), json, getAuthTokenHolder().getToken() + .getProject().getId()); + String jsonRes = response.body().string(); + int code = response.code(); + Logger.logString("Response: "+jsonRes); + Logger.logString("Response code: "+code); + assertEquals("Plan creation failed: Response code not matched: ", code, 200); + JSONObject jsonObject = new JSONObject(jsonRes); + + String id = jsonObject.getJSONObject("plan").get("id").toString(); + assertNotNull(id,"Id is null: "); + + Response responseRun = getHttpHandler().runPlans(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), id, getAuthTokenHolder().getToken() + .getProject().getId()); + String jsonResRun = responseRun.body().string(); + int codeRun = responseRun.code(); + Logger.logString("Response: "+jsonResRun); + Logger.logString("Response code: "+codeRun); + assertEquals("Run plan creation failed: Response code not matched: ", codeRun, 200); + String jobId = new JSONObject(jsonResRun).get("jobId").toString(); + + Response responseGetJob = getHttpHandler().getJob(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), jobId, getAuthTokenHolder().getToken() + .getProject().getId()); + String jsonResGetJob = responseGetJob.body().string(); + int codeGetJob = responseGetJob.code(); + Logger.logString("Response: "+jsonResGetJob); + Logger.logString("Response code: "+codeGetJob); + assertEquals("Get job id failed: Response code not matched: ", codeGetJob, 200); + String status = new JSONObject(jsonResGetJob).getJSONObject("job").get("status").toString(); + Logger.logString("Status: "+ status); + } + + @Test + @Order(4) + @DisplayName("Test creating plan with immediately using empty request body") + public void testCreatePlanUsingEmptyRequestBody() throws IOException { + Gson gson = new Gson(); + List listOfIBucketInputs = + Utils.listFilesMatchingBeginsWithPatternInPath("bucket", CREATE_MIGRATION_PATH); + assertNotNull(listOfIBucketInputs); + SourceConnInput sourceConnInput = new SourceConnInput(); + sourceConnInput.setBucketName(""); + sourceConnInput.setStorType(""); + DestConnInput destConnInput = new DestConnInput(); + destConnInput.setBucketName(""); + destConnInput.setStorType(""); + Filter filter = new Filter(); + PlaneRequestInput planeRequestInput = new PlaneRequestInput(); + planeRequestInput.setName(""); + planeRequestInput.setDescription(""); + planeRequestInput.setType(""); + planeRequestInput.setSourceConn(sourceConnInput); + planeRequestInput.setDestConn(destConnInput); + planeRequestInput.setFilter(filter); + planeRequestInput.setRemainSource(true); + String json = gson.toJson(planeRequestInput); + Logger.logString("Request Json: "+json); + + Response response = getHttpHandler().createPlans(getAuthTokenHolder() + .getResponseHeaderSubjectToken(), json, getAuthTokenHolder().getToken() + .getProject().getId()); + String jsonRes = response.body().string(); + int code = response.code(); + Logger.logString("Response: "+jsonRes); + Logger.logString("Response code: "+code); + assertEquals("Plan creation failed request body empty: Response code not matched: ", code, 400); + } + + @Test + @Order(5) + @DisplayName("Test after migration download image from source and destination bucket") + public void testSourceDesBucketDownloadObject() throws IOException { + testDownloadObject(CREATE_MIGRATION_PATH, "Migration_obj.jpg"); + } +}