From: Ed Warnicke Date: Thu, 16 Apr 2015 03:55:18 +0000 (+0000) Subject: Merge "Bug 2358: Fixed warnings in Restconf" X-Git-Tag: release/lithium~262 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=f9a9cd1ea40d2477ccb16b03c71a87595226595a;hp=84df280755136f2d24420f6bf591437a68510f2c Merge "Bug 2358: Fixed warnings in Restconf" --- diff --git a/features/netconf/src/main/resources/features.xml b/features/netconf/src/main/resources/features.xml index dbd940f5e6..80b2e36211 100644 --- a/features/netconf/src/main/resources/features.xml +++ b/features/netconf/src/main/resources/features.xml @@ -36,6 +36,7 @@ odl-netconf-mapping-api mvn:org.opendaylight.yangtools/yang-model-api/${yangtools.version} + mvn:org.opendaylight.yangtools/yang-data-api/${yangtools.version} mvn:org.opendaylight.controller/netconf-util/${project.version} diff --git a/karaf/karaf-parent/pom.xml b/karaf/karaf-parent/pom.xml index 2d914b3fe2..958f2b884c 100644 --- a/karaf/karaf-parent/pom.xml +++ b/karaf/karaf-parent/pom.xml @@ -23,6 +23,7 @@ and is available at http://www.eclipse.org/legal/epl-v10.html 1.1.0-SNAPSHOT 1.5.0-SNAPSHOT + standard @@ -47,6 +48,15 @@ and is available at http://www.eclipse.org/legal/epl-v10.html + + + org.apache.karaf.features + standard + ${karaf.version} + xml + features + runtime + org.opendaylight.controller diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/bin/instance b/karaf/opendaylight-karaf-resources/src/main/resources/bin/instance old mode 100755 new mode 100644 index 7288042bab..27772fd255 --- a/karaf/opendaylight-karaf-resources/src/main/resources/bin/instance +++ b/karaf/opendaylight-karaf-resources/src/main/resources/bin/instance @@ -85,7 +85,7 @@ unlimitFD() { # Increase the maximum file descriptors if we can if [ "$os400" = "false" ] && [ "$cygwin" = "false" ]; then MAX_FD_LIMIT=`ulimit -H -n` - if [ "$MAX_FD_LIMIT" != 'unlimited' ]; then + if [ "$MAX_FD_LIMIT" != 'unlimited' ]; then if [ $? -eq 0 ]; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then # use the system max @@ -188,7 +188,7 @@ locateJava() { fi if [ "x$JAVA_HOME" = "x" ] && [ "$darwin" = "true" ]; then - JAVA_HOME="$(/usr/libexec/java_home)" + JAVA_HOME="$(/usr/libexec/java_home -v 1.7)" fi if [ "x$JAVA" = "x" ] && [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` @@ -202,7 +202,7 @@ locateJava() { else warn "JAVA_HOME not set; results may vary" JAVA=`type java` - JAVA=`expr "$JAVA" : '.*is \(.*\)$'` + JAVA=`expr "$JAVA" : '.* \(/.*\)$'` if [ "x$JAVA" = "x" ]; then die "java command not found" fi @@ -234,6 +234,10 @@ setupDebugOptions() { fi export JAVA_OPTS + if [ "x$EXTRA_JAVA_OPTS" != "x" ]; then + JAVA_OPTS="$JAVA_OPTS $EXTRA_JAVA_OPTS" + fi + # Set Debug options if enabled if [ "x$KARAF_DEBUG" != "x" ]; then # Use the defaults if JAVA_DEBUG_OPTS was not set @@ -280,7 +284,12 @@ setupDefaults() { CLASSPATH="$CLASSPATH:$file" fi done - DEFAULT_JAVA_DEBUG_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005" + + DEFAULT_JAVA_DEBUG_PORT="5005" + if [ "x$JAVA_DEBUG_PORT" = "x" ]; then + JAVA_DEBUG_PORT="$DEFAULT_JAVA_DEBUG_PORT" + fi + DEFAULT_JAVA_DEBUG_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$JAVA_DEBUG_PORT" ## ## TODO: Move to conf/profiler/yourkit.{sh|cmd} @@ -327,7 +336,7 @@ init() { run() { - CLASSPATH="${KARAF_HOME}/system/org/apache/karaf/instance/org.apache.karaf.instance.command/3.0.1/org.apache.karaf.instance.command-3.0.1.jar:${KARAF_HOME}/system/org/apache/karaf/instance/org.apache.karaf.instance.core/3.0.1/org.apache.karaf.instance.core-3.0.1.jar:${KARAF_HOME}/system/org/apache/karaf/shell/org.apache.karaf.shell.console/3.0.1/org.apache.karaf.shell.console-3.0.1.jar:${KARAF_HOME}/system/org/apache/karaf/shell/org.apache.karaf.shell.table/3.0.1/org.apache.karaf.shell.table-3.0.1.jar:${KARAF_HOME}/system/org/apache/aries/blueprint/org.apache.aries.blueprint.api/1.0.0/org.apache.aries.blueprint.api-1.0.0.jar:${KARAF_HOME}/system/org/apache/aries/blueprint/org.apache.aries.blueprint.core/1.4.0/org.apache.aries.blueprint.core-1.4.0.jar:${KARAF_HOME}/system/org/apache/aries/blueprint/org.apache.aries.blueprint.cm/1.0.3/org.apache.aries.blueprint.cm-1.0.3.jar:${KARAF_HOME}/system/org/ops4j/pax/logging/pax-logging-api/1.7.2/pax-logging-api-1.7.2.jar:${KARAF_HOME}/system/org/apache/felix/org.apache.felix.framework/4.2.1/org.apache.felix.framework-4.2.1.jar:${KARAF_HOME}/system/jline/jline/2.11/jline-2.11.jar:$CLASSPATH" + CLASSPATH="${KARAF_HOME}/system/org/apache/karaf/instance/org.apache.karaf.instance.command/3.0.3/org.apache.karaf.instance.command-3.0.3.jar:${KARAF_HOME}/system/org/apache/karaf/instance/org.apache.karaf.instance.core/3.0.3/org.apache.karaf.instance.core-3.0.3.jar:${KARAF_HOME}/system/org/apache/karaf/shell/org.apache.karaf.shell.console/3.0.3/org.apache.karaf.shell.console-3.0.3.jar:${KARAF_HOME}/system/org/apache/karaf/shell/org.apache.karaf.shell.table/3.0.3/org.apache.karaf.shell.table-3.0.3.jar:${KARAF_HOME}/system/org/apache/aries/blueprint/org.apache.aries.blueprint.api/1.0.1/org.apache.aries.blueprint.api-1.0.1.jar:${KARAF_HOME}/system/org/apache/aries/blueprint/org.apache.aries.blueprint.core/1.4.2/org.apache.aries.blueprint.core-1.4.2.jar:${KARAF_HOME}/system/org/apache/aries/blueprint/org.apache.aries.blueprint.cm/1.0.5/org.apache.aries.blueprint.cm-1.0.5.jar:${KARAF_HOME}/system/org/ops4j/pax/logging/pax-logging-api/1.8.1/pax-logging-api-1.8.1.jar:${KARAF_HOME}/system/org/apache/felix/org.apache.felix.framework/4.2.1/org.apache.felix.framework-4.2.1.jar:${KARAF_HOME}/system/jline/jline/2.12/jline-2.12.jar:$CLASSPATH" if $cygwin; then KARAF_HOME=`cygpath --path --windows "$KARAF_HOME"` @@ -346,4 +355,3 @@ main() { } main "$@" - diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/bin/instance.bat b/karaf/opendaylight-karaf-resources/src/main/resources/bin/instance.bat index 49c2c0fb4e..2ac8db1897 100644 --- a/karaf/opendaylight-karaf-resources/src/main/resources/bin/instance.bat +++ b/karaf/opendaylight-karaf-resources/src/main/resources/bin/instance.bat @@ -95,7 +95,7 @@ if "%KARAF_ETC%" == "" ( ) set DEFAULT_JAVA_OPTS= -set DEFAULT_JAVA_DEBUG_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 +set DEFAULT_JAVA_DEBUG_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 rem Support for loading native libraries set PATH=%PATH%;%KARAF_BASE%\lib;%KARAF_HOME%\lib @@ -113,11 +113,15 @@ if not "%JAVA%" == "" goto :Check_JAVA_END if "%JAVA_OPTS%" == "" set JAVA_OPTS=%DEFAULT_JAVA_OPTS% +if "%EXTRA_JAVA_OPTS%" == "" goto :KARAF_EXTRA_JAVA_OPTS_END + set JAVA_OPTS="%JAVA_OPTS% %EXTRA_JAVA_OPTS%" +:KARAF_EXTRA_JAVA_OPTS_END + if "%KARAF_DEBUG%" == "" goto :KARAF_DEBUG_END rem Use the defaults if JAVA_DEBUG_OPTS was not set if "%JAVA_DEBUG_OPTS%" == "" set JAVA_DEBUG_OPTS=%DEFAULT_JAVA_DEBUG_OPTS% - set "JAVA_OPTS=%JAVA_DEBUG_OPTS% %JAVA_OPTS%" + set JAVA_OPTS="%JAVA_DEBUG_OPTS% %JAVA_OPTS%" call :warn Enabling Java debug options: %JAVA_DEBUG_OPTS% :KARAF_DEBUG_END @@ -135,7 +139,7 @@ goto :EOF :CLASSPATH_END -set CLASSPATH=%KARAF_HOME%\system\org\apache\karaf\instance\org.apache.karaf.instance.command\3.0.1\org.apache.karaf.instance.command-3.0.1.jar;%KARAF_HOME%\system\org\apache\karaf\instance\org.apache.karaf.instance.core\3.0.1\org.apache.karaf.instance.core-3.0.1.jar;%KARAF_HOME%\system\org\apache\karaf\shell\org.apache.karaf.shell.console\3.0.1\org.apache.karaf.shell.console-3.0.1.jar;%KARAF_HOME%\system\org\apache\karaf\shell\org.apache.karaf.shell.table\3.0.1\org.apache.karaf.shell.table-3.0.1.jar;%KARAF_HOME%\system\org\apache\aries\blueprint\org.apache.aries.blueprint.api\1.0.0\org.apache.aries.blueprint.api-1.0.0.jar;%KARAF_HOME%\system\org\apache\aries\blueprint\org.apache.aries.blueprint.core\1.4.0\org.apache.aries.blueprint.core-1.4.0.jar;%KARAF_HOME%\system\org\apache\aries\blueprint\org.apache.aries.blueprint.cm\1.0.3\org.apache.aries.blueprint.cm-1.0.3.jar;%KARAF_HOME%\system\org\ops4j\pax\logging\pax-logging-api\1.7.2\pax-logging-api-1.7.2.jar;%KARAF_HOME%\system\org\apache\felix\org.apache.felix.framework\4.2.1\org.apache.felix.framework-4.2.1.jar;%KARAF_HOME%\system\jline\jline\2.11\jline-2.11.jar;%CLASSPATH% +set CLASSPATH=%KARAF_HOME%\system\org\apache\karaf\instance\org.apache.karaf.instance.command\3.0.3\org.apache.karaf.instance.command-3.0.3.jar;%KARAF_HOME%\system\org\apache\karaf\instance\org.apache.karaf.instance.core\3.0.3\org.apache.karaf.instance.core-3.0.3.jar;%KARAF_HOME%\system\org\apache\karaf\shell\org.apache.karaf.shell.console\3.0.3\org.apache.karaf.shell.console-3.0.3.jar;%KARAF_HOME%\system\org\apache\karaf\shell\org.apache.karaf.shell.table\3.0.3\org.apache.karaf.shell.table-3.0.3.jar;%KARAF_HOME%\system\org\apache\aries\blueprint\org.apache.aries.blueprint.api\1.0.1\org.apache.aries.blueprint.api-1.0.1.jar;%KARAF_HOME%\system\org\apache\aries\blueprint\org.apache.aries.blueprint.core\1.4.2\org.apache.aries.blueprint.core-1.4.2.jar;%KARAF_HOME%\system\org\apache\aries\blueprint\org.apache.aries.blueprint.cm\1.0.5\org.apache.aries.blueprint.cm-1.0.5.jar;%KARAF_HOME%\system\org\ops4j\pax\logging\pax-logging-api\1.8.1\pax-logging-api-1.8.1.jar;%KARAF_HOME%\system\org\apache\felix\org.apache.felix.framework\4.2.1\org.apache.felix.framework-4.2.1.jar;%KARAF_HOME%\system\jline\jline\2.12\jline-2.12.jar;%CLASSPATH% :EXECUTE if "%SHIFT%" == "true" SET ARGS=%2 %3 %4 %5 %6 %7 %8 @@ -148,4 +152,3 @@ rem # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # :END endlocal - diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/bin/karaf b/karaf/opendaylight-karaf-resources/src/main/resources/bin/karaf index cad052a8ce..23fbbec452 100755 --- a/karaf/opendaylight-karaf-resources/src/main/resources/bin/karaf +++ b/karaf/opendaylight-karaf-resources/src/main/resources/bin/karaf @@ -16,8 +16,8 @@ # limitations under the License. # -DIRNAME=`dirname $0` -PROGNAME=`basename $0` +DIRNAME=`dirname "$0"` +PROGNAME=`basename "$0"` # # Sourcing environment settings for karaf similar to tomcats setenv @@ -92,7 +92,7 @@ unlimitFD() { # Increase the maximum file descriptors if we can if [ "$os400" = "false" ] && [ "$cygwin" = "false" ]; then MAX_FD_LIMIT=`ulimit -H -n` - if [ "$MAX_FD_LIMIT" != 'unlimited' ]; then + if [ "$MAX_FD_LIMIT" != 'unlimited' ]; then if [ $? -eq 0 ]; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then # use the system max @@ -195,7 +195,7 @@ locateJava() { fi if [ "x$JAVA_HOME" = "x" ] && [ "$darwin" = "true" ]; then - JAVA_HOME="$(/usr/libexec/java_home)" + JAVA_HOME="$(/usr/libexec/java_home -v 1.7)" fi if [ "x$JAVA" = "x" ] && [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` @@ -209,7 +209,7 @@ locateJava() { else warn "JAVA_HOME not set; results may vary" JAVA=`type java` - JAVA=`expr "$JAVA" : '.*is \(.*\)$'` + JAVA=`expr "$JAVA" : '.* \(/.*\)$'` if [ "x$JAVA" = "x" ]; then die "java command not found" fi @@ -237,7 +237,7 @@ detectJVM() { checkJvmVersion() { # echo "`$JAVA -version`" - VERSION=`$JAVA -version 2>&1 | egrep '"([0-9].[0-9]\..*[0-9])"' | awk '{print substr($3,2,length($3)-2)}' | awk '{print substr($1, 3, 3)}' | sed -e 's;\.;;g'` + VERSION=`$JAVA -version 2>&1 | egrep '"([0-9].[0-9]\..*[0-9]).*"' | awk '{print substr($3,2,length($3)-2)}' | awk '{print substr($1, 3, 3)}' | sed -e 's;\.;;g'` # echo $VERSION if [ "$VERSION" -lt "60" ]; then echo "JVM must be greater than 1.6" @@ -251,6 +251,10 @@ setupDebugOptions() { fi export JAVA_OPTS + if [ "x$EXTRA_JAVA_OPTS" != "x" ]; then + JAVA_OPTS="$JAVA_OPTS $EXTRA_JAVA_OPTS" + fi + # Set Debug options if enabled if [ "x$KARAF_DEBUG" != "x" ]; then # Ignore DEBUG in case of stop or client mode @@ -304,7 +308,12 @@ setupDefaults() { CLASSPATH="$CLASSPATH:$file" fi done - DEFAULT_JAVA_DEBUG_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005" + + DEFAULT_JAVA_DEBUG_PORT="5005" + if [ "x$JAVA_DEBUG_PORT" = "x" ]; then + JAVA_DEBUG_PORT="$DEFAULT_JAVA_DEBUG_PORT" + fi + DEFAULT_JAVA_DEBUG_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$JAVA_DEBUG_PORT" ## ## TODO: Move to conf/profiler/yourkit.{sh|cmd} @@ -340,7 +349,7 @@ init() { # Determine the JVM vendor detectJVM - + # Determine the JVM version >= 1.6 checkJvmVersion diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/bin/karaf.bat b/karaf/opendaylight-karaf-resources/src/main/resources/bin/karaf.bat index a45087730f..9c278c3b9a 100644 --- a/karaf/opendaylight-karaf-resources/src/main/resources/bin/karaf.bat +++ b/karaf/opendaylight-karaf-resources/src/main/resources/bin/karaf.bat @@ -96,26 +96,9 @@ if "%KARAF_ETC%" == "" ( set LOCAL_CLASSPATH=%CLASSPATH% set JAVA_MODE=-server -if not exist "%JAVA_HOME%\bin\server\jvm.dll" ( - if not exist "%JAVA_HOME%\jre\bin\server\jvm.dll" ( - echo WARNING: Running karaf on a Java HotSpot Client VM because server-mode is not available. - echo Install Java Developer Kit to fix this. - echo For more details see http://java.sun.com/products/hotspot/whitepaper.html#client - set JAVA_MODE=-client - ) -) -set DEFAULT_JAVA_OPTS=%JAVA_MODE% -Xms%JAVA_MIN_MEM% -Xmx%JAVA_MAX_MEM% -Dderby.system.home="%KARAF_DATA%\derby" -Dderby.storage.fileSyncTransactionLog=true -Dcom.sun.management.jmxremote -XX:+UnlockDiagnosticVMOptions -XX:+UnsyncloadClass - -rem Check some easily accessible MIN/MAX params for JVM mem usage -if not "%JAVA_PERM_MEM%" == "" ( - set DEFAULT_JAVA_OPTS=%DEFAULT_JAVA_OPTS% -XX:PermSize=%JAVA_PERM_MEM% -) -if not "%JAVA_MAX_PERM_MEM%" == "" ( - set DEFAULT_JAVA_OPTS=%DEFAULT_JAVA_OPTS% -XX:MaxPermSize=%JAVA_MAX_PERM_MEM% -) set CLASSPATH=%LOCAL_CLASSPATH%;%KARAF_BASE%\conf -set DEFAULT_JAVA_DEBUG_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 +set DEFAULT_JAVA_DEBUG_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 if "%LOCAL_CLASSPATH%" == "" goto :KARAF_CLASSPATH_EMPTY set CLASSPATH=%LOCAL_CLASSPATH%;%KARAF_BASE%\conf @@ -206,8 +189,8 @@ if not "%JAVA%" == "" goto :Check_JAVA_END ) if not exist "%JAVA_HOME%" ( goto TryRegJDK - ) - goto TryJDKEnd + ) + goto TryJDKEnd :TryRegJDK rem try getting the JAVA_HOME from registry FOR /F "usebackq tokens=3*" %%A IN (`REG QUERY "HKLM\Software\JavaSoft\Java Development Kit" /v CurrentVersion`) DO ( @@ -219,7 +202,7 @@ if not "%JAVA%" == "" goto :Check_JAVA_END if not exist "%JAVA_HOME%" ( call :warn Unable to retrieve JAVA_HOME from Registry ) - goto TryJDKEnd + goto TryJDKEnd :TryJDKEnd if not exist "%JAVA_HOME%" ( call :warn JAVA_HOME is not valid: "%JAVA_HOME%" @@ -228,15 +211,37 @@ if not "%JAVA%" == "" goto :Check_JAVA_END set JAVA=%JAVA_HOME%\bin\java :Check_JAVA_END +if not exist "%JAVA_HOME%\bin\server\jvm.dll" ( + if not exist "%JAVA_HOME%\jre\bin\server\jvm.dll" ( + echo WARNING: Running Karaf on a Java HotSpot Client VM because server-mode is not available. + echo Install Java Developer Kit to fix this. + echo For more details see http://java.sun.com/products/hotspot/whitepaper.html#client + set JAVA_MODE=-client + ) +) +set DEFAULT_JAVA_OPTS=%JAVA_MODE% -Xms%JAVA_MIN_MEM% -Xmx%JAVA_MAX_MEM% -Dderby.system.home="%KARAF_DATA%\derby" -Dderby.storage.fileSyncTransactionLog=true -Dcom.sun.management.jmxremote -XX:+UnlockDiagnosticVMOptions -XX:+UnsyncloadClass + +rem Check some easily accessible MIN/MAX params for JVM mem usage +if not "%JAVA_PERM_MEM%" == "" ( + set DEFAULT_JAVA_OPTS=%DEFAULT_JAVA_OPTS% -XX:PermSize=%JAVA_PERM_MEM% +) +if not "%JAVA_MAX_PERM_MEM%" == "" ( + set DEFAULT_JAVA_OPTS=%DEFAULT_JAVA_OPTS% -XX:MaxPermSize=%JAVA_MAX_PERM_MEM% +) + if "%JAVA_OPTS%" == "" set JAVA_OPTS=%DEFAULT_JAVA_OPTS% +if "%EXTRA_JAVA_OPTS%" == "" goto :KARAF_EXTRA_JAVA_OPTS_END + set JAVA_OPTS=%JAVA_OPTS% %EXTRA_JAVA_OPTS% +:KARAF_EXTRA_JAVA_OPTS_END + if "%KARAF_DEBUG%" == "" goto :KARAF_DEBUG_END if "%1" == "stop" goto :KARAF_DEBUG_END if "%1" == "client" goto :KARAF_DEBUG_END rem Use the defaults if JAVA_DEBUG_OPTS was not set if "%JAVA_DEBUG_OPTS%" == "" set JAVA_DEBUG_OPTS=%DEFAULT_JAVA_DEBUG_OPTS% - set "JAVA_OPTS=%JAVA_DEBUG_OPTS% %JAVA_OPTS%" + set JAVA_OPTS=%JAVA_DEBUG_OPTS% %JAVA_OPTS% call :warn Enabling Java debug options: %JAVA_DEBUG_OPTS% :KARAF_DEBUG_END @@ -314,7 +319,7 @@ if "%KARAF_PROFILER%" == "" goto :RUN :EXECUTE_DEBUG if "%JAVA_DEBUG_OPTS%" == "" set JAVA_DEBUG_OPTS=%DEFAULT_JAVA_DEBUG_OPTS% - set "JAVA_OPTS=%JAVA_DEBUG_OPTS% %JAVA_OPTS%" + set JAVA_OPTS=%JAVA_DEBUG_OPTS% %JAVA_OPTS% shift goto :RUN_LOOP @@ -333,4 +338,3 @@ endlocal if not "%PAUSE%" == "" pause :END_NO_PAUSE - diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/bin/setenv b/karaf/opendaylight-karaf-resources/src/main/resources/bin/setenv old mode 100755 new mode 100644 index 947c65f6bd..42330fa933 --- a/karaf/opendaylight-karaf-resources/src/main/resources/bin/setenv +++ b/karaf/opendaylight-karaf-resources/src/main/resources/bin/setenv @@ -32,7 +32,7 @@ # # -# The following section shows the possible configuration options for the default +# The following section shows the possible configuration options for the default # karaf scripts # # export JAVA_HOME # Location of Java installation @@ -40,16 +40,17 @@ # export JAVA_MAX_MEM # Maximum memory for the JVM # export JAVA_PERM_MEM # Minimum perm memory for the JVM # export JAVA_MAX_PERM_MEM # Maximum perm memory for the JVM +# export EXTRA_JAVA_OPTS # Additional JVM options # export KARAF_HOME # Karaf home folder # export KARAF_DATA # Karaf data folder # export KARAF_BASE # Karaf base folder # export KARAF_ETC # Karaf etc folder # export KARAF_OPTS # Additional available Karaf options # export KARAF_DEBUG # Enable debug mode +# export KARAF_REDIRECT # Enable/set the std/err redirection when using bin/start if [ "x$JAVA_MAX_PERM_MEM" = "x" ]; then export JAVA_MAX_PERM_MEM="512m" fi if [ "x$JAVA_MAX_MEM" = "x" ]; then export JAVA_MAX_MEM="2048m" fi - diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/bin/setenv.bat b/karaf/opendaylight-karaf-resources/src/main/resources/bin/setenv.bat index 7c6192002c..66a25a3a84 100644 --- a/karaf/opendaylight-karaf-resources/src/main/resources/bin/setenv.bat +++ b/karaf/opendaylight-karaf-resources/src/main/resources/bin/setenv.bat @@ -48,6 +48,8 @@ rem Minimum perm memory for the JVM rem SET JAVA_PERM_MEM rem Maximum perm memory for the JVM rem SET JAVA_MAX_PERM_MEM +rem Additional JVM options +rem SET EXTRA_JAVA_OPTS rem Karaf home folder rem SET KARAF_HOME rem Karaf data folder diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/etc/custom.properties b/karaf/opendaylight-karaf-resources/src/main/resources/etc/custom.properties index 4a8f5ae795..e726b800d2 100644 --- a/karaf/opendaylight-karaf-resources/src/main/resources/etc/custom.properties +++ b/karaf/opendaylight-karaf-resources/src/main/resources/etc/custom.properties @@ -132,8 +132,14 @@ java.util.logging.config.file=configuration/tomcat-logging.properties hosttracker.keyscheme=IP # LISP Flow Mapping configuration -# Map-Register messages overwrite existing RLOC sets in EID-to-RLOC mappings +# Map-Register messages overwrite existing RLOC sets in EID-to-RLOC mappings (default: true) lisp.mappingOverwrite = true -# Enable the Solicit-Map-Request (SMR) mechanism -lisp.smr = false +# Enable the Solicit-Map-Request (SMR) mechanism (default: true) +lisp.smr = true +# Choose policy for Explicit Locator Path (ELP) handling +# There are three options: +# default: don't add or remove locator records, return mapping as-is +# both: keep the ELP, but add the next hop as a standalone non-LCAF locator with a lower priority +# replace: remove the ELP, add the next hop as a standalone non-LCAF locator +lisp.elpPolicy = default diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/etc/jre.properties b/karaf/opendaylight-karaf-resources/src/main/resources/etc/jre.properties index f32078a953..bfdef62d0d 100644 --- a/karaf/opendaylight-karaf-resources/src/main/resources/etc/jre.properties +++ b/karaf/opendaylight-karaf-resources/src/main/resources/etc/jre.properties @@ -27,8 +27,8 @@ jre-1.6= \ javax.accessibility, \ javax.activation;version="1.1", \ javax.activity, \ - javax.annotation;version="1.1", \ - javax.annotation.processing;version="1.1", \ + javax.annotation;version="1.0", \ + javax.annotation.processing;version="1.6", \ javax.crypto, \ javax.crypto.interfaces, \ javax.crypto.spec, \ @@ -182,7 +182,7 @@ jre-1.6= \ org.w3c.dom.xpath, \ org.xml.sax, \ org.xml.sax.ext, \ - org.xml.sax.helpers, \ + org.xml.sax.helpers, \ sun.misc # Standard package set. Note that: @@ -191,8 +191,8 @@ jre-1.7= \ javax.accessibility, \ javax.activation;version="1.1", \ javax.activity, \ - javax.annotation;version="1.2", \ - javax.annotation.processing;version="1.2", \ + javax.annotation;version="1.0", \ + javax.annotation.processing;version="1.6", \ javax.crypto, \ javax.crypto.interfaces, \ javax.crypto.spec, \ @@ -346,15 +346,15 @@ jre-1.7= \ org.w3c.dom.xpath, \ org.xml.sax, \ org.xml.sax.ext, \ - org.xml.sax.helpers, \ + org.xml.sax.helpers, \ sun.misc jre-1.8= \ javax.accessibility, \ javax.activation;version="1.1", \ javax.activity, \ - javax.annotation;version="1.2", \ - javax.annotation.processing;version="1.2", \ + javax.annotation;version="1.0", \ + javax.annotation.processing;version="1.6", \ javax.crypto, \ javax.crypto.interfaces, \ javax.crypto.spec, \ @@ -465,6 +465,39 @@ jre-1.8= \ javax.xml.ws.wsaddressing;version="2.2", \ javax.xml.ws.spi.http;version="2.2", \ javax.xml.xpath, \ + javafx.animation, \ + javafx.application, \ + javafx.beans, \ + javafx.beans.binding, \ + javafx.beans.property, \ + javafx.beans.property.adapter, \ + javafx.beans.value, \ + javafx.collections, \ + javafx.concurrent, \ + javafx.css, \ + javafx.embed.swing, \ + javafx.embed.swt, \ + javafx.event, \ + javafx.fxml, \ + javafx.geometry, \ + javafx.scene, \ + javafx.scene.canvas, \ + javafx.scene.chart, \ + javafx.scene.control, \ + javafx.scene.control.cell, \ + javafx.scene.effect, \ + javafx.scene.image, \ + javafx.scene.input, \ + javafx.scene.layout, \ + javafx.scene.media, \ + javafx.scene.paint, \ + javafx.scene.shape, \ + javafx.scene.text, \ + javafx.scene.transform, \ + javafx.scene.web, \ + javafx.stage, \ + javafx.util, \ + javafx.util.converter, \ org.ietf.jgss, \ org.omg.CORBA, \ org.omg.CORBA_2_3, \ @@ -508,5 +541,5 @@ jre-1.8= \ org.w3c.dom.xpath, \ org.xml.sax, \ org.xml.sax.ext, \ - org.xml.sax.helpers, \ + org.xml.sax.helpers, \ sun.misc diff --git a/karaf/opendaylight-karaf/pom.xml b/karaf/opendaylight-karaf/pom.xml index 9c02a9d1b1..68238bbc12 100644 --- a/karaf/opendaylight-karaf/pom.xml +++ b/karaf/opendaylight-karaf/pom.xml @@ -87,13 +87,6 @@ xml runtime - - org.opendaylight.controller - features-flow - features - xml - runtime - org.opendaylight.controller features-restconf diff --git a/opendaylight/md-sal/mdsal-artifacts/pom.xml b/opendaylight/md-sal/mdsal-artifacts/pom.xml index c115dcd62a..d0b9f48873 100644 --- a/opendaylight/md-sal/mdsal-artifacts/pom.xml +++ b/opendaylight/md-sal/mdsal-artifacts/pom.xml @@ -177,6 +177,23 @@ sal-remoterpc-connector ${project.version} + + org.opendaylight.controller + sal-akka-raft + ${project.version} + + + org.opendaylight.controller + sal-akka-raft + ${project.version} + test-jar + test + + + org.opendaylight.controller + sal-akka-raft-example + ${project.version} + @@ -219,14 +236,6 @@ model-flow-statistics ${project.version} - - org.opendaylight.controller - features-flow - ${project.version} - features - xml - runtime - diff --git a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleFactoryTest.java b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleFactoryTest.java index 7db7dcc333..d06a1a8273 100644 --- a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleFactoryTest.java +++ b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleFactoryTest.java @@ -49,4 +49,4 @@ public class MessageBusAppImplModuleFactoryTest { assertNotNull("Module has not been created correctly.", messageBusAppImplModuleFactory.createModule("instanceName1", dependencyResolverMock, dynamicMBeanWithInstanceMock, bundleContextMock)); } -} +} \ No newline at end of file diff --git a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleTest.java b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleTest.java index 85d1a1b109..e26502f949 100644 --- a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleTest.java +++ b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleTest.java @@ -7,17 +7,37 @@ */ package org.opendaylight.controller.config.yang.messagebus.app.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.mock; - import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.opendaylight.controller.config.api.DependencyResolver; +import org.opendaylight.controller.config.api.JmxAttribute; import org.opendaylight.controller.config.api.ModuleIdentifier; +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.MountPointService; +import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService; +import org.opendaylight.controller.md.sal.dom.api.DOMNotificationPublishService; +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.binding.api.BindingAwareProvider; +import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry; +import org.opendaylight.controller.sal.core.api.Broker; +import org.opendaylight.controller.sal.core.api.Provider; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.osgi.framework.BundleContext; +import javax.management.ObjectName; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + public class MessageBusAppImplModuleTest { MessageBusAppImplModule messageBusAppImplModule; @@ -55,5 +75,34 @@ public class MessageBusAppImplModuleTest { assertEquals("Set and/or get method/s don't work correctly.", bundleContext, messageBusAppImplModule.getBundleContext()); } - //TODO: create MessageBusAppImplModule.createInstance test -} + @Test + public void createInstanceTest() throws Exception{ + org.opendaylight.controller.sal.binding.api.BindingAwareBroker bindingAwareBrokerMock = mock(org.opendaylight.controller.sal.binding.api.BindingAwareBroker.class); + Broker brokerMock = mock(Broker.class); + doReturn(brokerMock).when(dependencyResolverMock).resolveInstance(eq(org.opendaylight.controller.sal.core.api.Broker.class), any(ObjectName.class), any(JmxAttribute.class)); + doReturn(bindingAwareBrokerMock).when(dependencyResolverMock).resolveInstance(eq(org.opendaylight.controller.sal.binding.api.BindingAwareBroker.class), any(ObjectName.class), any(JmxAttribute.class)); + messageBusAppImplModule.resolveDependencies(); + + BindingAwareBroker.ProviderContext providerContext = mock(BindingAwareBroker.ProviderContext.class); + doReturn(providerContext).when(bindingAwareBrokerMock).registerProvider(any(BindingAwareProvider.class)); + Broker.ProviderSession providerSessionMock = mock(Broker.ProviderSession.class); + doReturn(providerSessionMock).when(brokerMock).registerProvider(any(Provider.class)); + DataBroker dataBrokerMock = mock(DataBroker.class); + doReturn(dataBrokerMock).when(providerContext).getSALService(eq(DataBroker.class)); + DOMNotificationPublishService domNotificationPublishServiceMock = mock(DOMNotificationPublishService.class); + doReturn(domNotificationPublishServiceMock).when(providerSessionMock).getService(DOMNotificationPublishService.class); + DOMMountPointService domMountPointServiceMock = mock(DOMMountPointService.class); + doReturn(domMountPointServiceMock).when(providerSessionMock).getService(DOMMountPointService.class); + MountPointService mountPointServiceMock = mock(MountPointService.class); + doReturn(mountPointServiceMock).when(providerContext).getSALService(eq(MountPointService.class)); + RpcProviderRegistry rpcProviderRegistryMock = mock(RpcProviderRegistry.class); + doReturn(rpcProviderRegistryMock).when(providerContext).getSALService(eq(RpcProviderRegistry.class)); + + WriteTransaction writeTransactionMock = mock(WriteTransaction.class); + doReturn(writeTransactionMock).when(dataBrokerMock).newWriteOnlyTransaction(); + doNothing().when(writeTransactionMock).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(DataObject.class), eq(true)); + + assertNotNull("EventSourceRegistryWrapper has not been created correctly.", messageBusAppImplModule.createInstance()); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceRegistrationImplTest.java b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceRegistrationImplTest.java new file mode 100644 index 0000000000..9cce623523 --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceRegistrationImplTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.messagebus.app.impl; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.controller.messagebus.spi.EventSource; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class EventSourceRegistrationImplTest { + + EventSourceRegistrationImplLocal eventSourceRegistrationImplLocal; + EventSourceTopology eventSourceTopologyMock; + + @BeforeClass + public static void initTestClass() throws IllegalAccessException, InstantiationException { + } + + @Before + public void setUp() throws Exception { + EventSource eventSourceMock = mock(EventSource.class); + eventSourceTopologyMock = mock(EventSourceTopology.class); + eventSourceRegistrationImplLocal = new EventSourceRegistrationImplLocal(eventSourceMock, eventSourceTopologyMock); + } + + @Test + public void removeRegistrationTest() { + eventSourceRegistrationImplLocal.removeRegistration(); + verify(eventSourceTopologyMock, times(1)).unRegister(any(EventSource.class)); + } + + + private class EventSourceRegistrationImplLocal extends EventSourceRegistrationImpl{ + + /** + * @param instance of EventSource that has been registered by {@link EventSourceRegistryImpl#registerEventSource(Node, org.opendaylight.controller.messagebus.spi.EventSource)} + * @param eventSourceTopology + */ + public EventSourceRegistrationImplLocal(EventSource instance, EventSourceTopology eventSourceTopology) { + super(instance, eventSourceTopology); + } + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopicTest.java b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopicTest.java index f369a128ad..9f513c464b 100644 --- a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopicTest.java +++ b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopicTest.java @@ -74,7 +74,7 @@ public class EventSourceTopicTest { nodeIdMock = mock(NodeId.class); doReturn(nodeIdMock).when(dataObjectMock).getId(); - doReturn("0").when(nodeIdMock).getValue(); + doReturn("nodeIdPattern1").when(nodeIdMock).getValue(); } @Test @@ -84,4 +84,4 @@ public class EventSourceTopicTest { verify(eventSourceServiceMock, times(1)).joinTopic(any(JoinTopicInput.class)); } -} +} \ No newline at end of file diff --git a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopologyTest.java b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopologyTest.java index ced2e1f01b..50ae4d9389 100644 --- a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopologyTest.java +++ b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopologyTest.java @@ -16,13 +16,16 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; @@ -57,6 +60,7 @@ public class EventSourceTopologyTest { CreateTopicInput createTopicInputMock; ListenerRegistration listenerRegistrationMock; NodeKey nodeKey; + RpcRegistration aggregatorRpcReg; @BeforeClass public static void initTestClass() throws IllegalAccessException, InstantiationException { @@ -76,7 +80,7 @@ public class EventSourceTopologyTest { } private void constructorTestHelper(){ - RpcRegistration aggregatorRpcReg = mock(RpcRegistration.class); + aggregatorRpcReg = mock(RpcRegistration.class); EventSourceService eventSourceService = mock(EventSourceService.class); doReturn(aggregatorRpcReg).when(rpcProviderRegistryMock).addRpcImplementation(eq(EventAggregatorService.class), any(EventSourceTopology.class)); doReturn(eventSourceService).when(rpcProviderRegistryMock).getRpcService(EventSourceService.class); @@ -87,11 +91,11 @@ public class EventSourceTopologyTest { doReturn(checkedFutureMock).when(writeTransactionMock).submit(); } -//TODO: create test for createTopic -// public void createTopicTest() throws Exception{ -// createTopicTestHelper(); -// assertNotNull("Topic has not been created correctly.", eventSourceTopology.createTopic(createTopicInputMock)); -// } + @Test + public void createTopicTest() throws Exception{ + topicTestHelper(); + assertNotNull("Topic has not been created correctly.", eventSourceTopology.createTopic(createTopicInputMock)); + } private void topicTestHelper() throws Exception{ constructorTestHelper(); @@ -138,6 +142,19 @@ public class EventSourceTopologyTest { assertNotNull("Instance has not been created correctly.", eventSourceTopology.destroyTopic(destroyTopicInput)); } + @Test + public void closeTest() throws Exception{ + constructorTestHelper(); + topicTestHelper(); + Map> localMap = getTopicListenerRegistrations(); + DataChangeListener dataChangeListenerMock = mock(DataChangeListener.class); + ListenerRegistration listenerListenerRegistrationMock = (ListenerRegistration) mock(ListenerRegistration.class); + localMap.put(dataChangeListenerMock, listenerListenerRegistrationMock); + eventSourceTopology.close(); + verify(aggregatorRpcReg, times(1)).close(); + verify(listenerListenerRegistrationMock, times(1)).close(); + } + @Test public void registerTest() throws Exception { topicTestHelper(); @@ -154,4 +171,46 @@ public class EventSourceTopologyTest { verify(routedRpcRegistrationMock, times(1)).registerPath(eq(NodeContext.class), any(KeyedInstanceIdentifier.class)); } -} + @Test + public void unregisterTest() throws Exception { + topicTestHelper(); + EventSource eventSourceMock = mock(EventSource.class); + NodeId nodeId = new NodeId("nodeIdValue1"); + nodeKey = new NodeKey(nodeId); + Map> localMap = getRoutedRpcRegistrations(); + NodeKey nodeKeyMock = mock(NodeKey.class); + doReturn(nodeKeyMock).when(eventSourceMock).getSourceNodeKey(); + BindingAwareBroker.RoutedRpcRegistration routedRpcRegistrationMock = (BindingAwareBroker.RoutedRpcRegistration) mock(BindingAwareBroker.RoutedRpcRegistration.class); + localMap.put(nodeKeyMock, routedRpcRegistrationMock); + eventSourceTopology.unRegister(eventSourceMock); + verify(routedRpcRegistrationMock, times(1)).close(); + } + + @Test + public void registerEventSourceTest() throws Exception { + topicTestHelper(); + Node nodeMock = mock(Node.class); + EventSource eventSourceMock = mock(EventSource.class); + NodeId nodeId = new NodeId("nodeIdValue1"); + nodeKey = new NodeKey(nodeId); + doReturn(nodeKey).when(nodeMock).getKey(); + doReturn(nodeKey).when(eventSourceMock).getSourceNodeKey(); + BindingAwareBroker.RoutedRpcRegistration routedRpcRegistrationMock = mock(BindingAwareBroker.RoutedRpcRegistration.class); + doReturn(routedRpcRegistrationMock).when(rpcProviderRegistryMock).addRoutedRpcImplementation(EventSourceService.class, eventSourceMock); + doNothing().when(routedRpcRegistrationMock).registerPath(eq(NodeContext.class), any(KeyedInstanceIdentifier.class)); + assertNotNull("Return value has not been created correctly.", eventSourceTopology.registerEventSource(eventSourceMock)); + } + + private Map getTopicListenerRegistrations() throws Exception{ + Field nesField = EventSourceTopology.class.getDeclaredField("topicListenerRegistrations"); + nesField.setAccessible(true); + return (Map) nesField.get(eventSourceTopology); + } + + private Map getRoutedRpcRegistrations() throws Exception{ + Field nesField = EventSourceTopology.class.getDeclaredField("routedRpcRegistrations"); + nesField.setAccessible(true); + return (Map) nesField.get(eventSourceTopology); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSourceManagerTest.java b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSourceManagerTest.java index 61fa30f40e..1d6b825c9f 100644 --- a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSourceManagerTest.java +++ b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSourceManagerTest.java @@ -84,11 +84,11 @@ public class NetconfEventSourceManagerTest { netconfEventSourceManager = NetconfEventSourceManager.create(dataBrokerMock, - domNotificationPublishServiceMock, - domMountPointServiceMock, - mountPointServiceMock, - eventSourceRegistry, - namespaceToStreamList); + domNotificationPublishServiceMock, + domMountPointServiceMock, + mountPointServiceMock, + eventSourceRegistry, + namespaceToStreamList); } @Test @@ -125,12 +125,14 @@ public class NetconfEventSourceManagerTest { Map mapUpdate = new HashMap<>(); InstanceIdentifier instanceIdentifierMock = mock(InstanceIdentifier.class); Node dataObjectMock = mock(Node.class); + if(create){ mapCreate.put(instanceIdentifierMock, dataObjectMock); } if(update){ mapUpdate.put(instanceIdentifierMock, dataObjectMock); } + doReturn(mapCreate).when(asyncDataChangeEventMock).getCreatedData(); doReturn(mapUpdate).when(asyncDataChangeEventMock).getUpdatedData(); NetconfNode netconfNodeMock = mock(NetconfNode.class); @@ -171,4 +173,4 @@ public class NetconfEventSourceManagerTest { doReturn(esrMock).when(eventSourceRegistry).registerEventSource(any(EventSource.class)); } -} +} \ No newline at end of file diff --git a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSourceTest.java b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSourceTest.java index 58da9e3eb1..ed9025780a 100644 --- a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSourceTest.java +++ b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSourceTest.java @@ -7,22 +7,8 @@ */ package org.opendaylight.controller.messagebus.app.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.lang.reflect.Field; -import java.net.URI; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - +import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.md.sal.binding.api.BindingService; @@ -53,21 +39,32 @@ import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaPath; -import com.google.common.base.Optional; -import com.google.common.util.concurrent.CheckedFuture; +import java.lang.reflect.Field; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; public class NetconfEventSourceTest { NetconfEventSource netconfEventSource; DOMMountPoint domMountPointMock; JoinTopicInput joinTopicInputMock; - AsyncDataChangeEvent asyncDataChangeEventMock; - Node dataObjectMock; @Before public void setUp() throws Exception { Map streamMap = new HashMap<>(); - streamMap.put("string1", "string2"); + streamMap.put("uriStr1", "string2"); domMountPointMock = mock(DOMMountPoint.class); DOMNotificationPublishService domNotificationPublishServiceMock = mock(DOMNotificationPublishService.class); MountPoint mountPointMock = mock(MountPoint.class); @@ -80,9 +77,9 @@ public class NetconfEventSourceTest { doReturn(rpcConsumerRegistryMock).when(onlyOptionalMock).get(); doReturn(notificationsServiceMock).when(rpcConsumerRegistryMock).getRpcService(NotificationsService.class); org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node node - = mock(org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node.class); + = mock(org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node.class); org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId nodeId - = new org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId("NodeId1"); + = new org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId("NodeId1"); doReturn(nodeId).when(node).getNodeId(); netconfEventSource = new NetconfEventSource(node, streamMap, domMountPointMock, domNotificationPublishServiceMock, mountPointMock); } @@ -143,7 +140,7 @@ public class NetconfEventSourceTest { doReturn(topicId).when(joinTopicInputMock).getTopicId(); NotificationPattern notificationPatternMock = mock(NotificationPattern.class); doReturn(notificationPatternMock).when(joinTopicInputMock).getNotificationPattern(); - doReturn("regexString1").when(notificationPatternMock).getValue(); + doReturn("uriStr1").when(notificationPatternMock).getValue(); SchemaContext schemaContextMock = mock(SchemaContext.class); doReturn(schemaContextMock).when(domMountPointMock).getSchemaContext(); @@ -165,6 +162,13 @@ public class NetconfEventSourceTest { doReturn(domNotificationServiceMock).when(domNotificationServiceOptionalMock).get(); ListenerRegistration listenerRegistrationMock = mock(ListenerRegistration.class); doReturn(listenerRegistrationMock).when(domNotificationServiceMock).registerNotificationListener(any(NetconfEventSource.class), any(List.class)); + + Optional optionalMock = (Optional) mock(Optional.class); + doReturn(optionalMock).when(domMountPointMock).getService(DOMRpcService.class); + DOMRpcService domRpcServiceMock = mock(DOMRpcService.class); + doReturn(domRpcServiceMock).when(optionalMock).get(); + CheckedFuture checkedFutureMock = mock(CheckedFuture.class); + doReturn(checkedFutureMock).when(domRpcServiceMock).invokeRpc(any(SchemaPath.class), any(ContainerNode.class)); } //TODO: create Test for NetConfEventSource#onNotification @@ -175,4 +179,4 @@ public class NetconfEventSourceTest { return (Set) nesField.get(netconfEventSource); } -} +} \ No newline at end of file diff --git a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/TopicDOMNotificationTest.java b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/TopicDOMNotificationTest.java index 6dacb9738a..b3f6438cc4 100644 --- a/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/TopicDOMNotificationTest.java +++ b/opendaylight/md-sal/messagebus-impl/src/test/java/org/opendaylight/controller/messagebus/app/impl/TopicDOMNotificationTest.java @@ -57,4 +57,4 @@ public class TopicDOMNotificationTest { String bodyString = "TopicDOMNotification [body=" + containerNodeBodyMockToString + "]"; assertEquals("String has not been created correctly.", bodyString, topicDOMNotification.toString()); } -} +} \ No newline at end of file diff --git a/opendaylight/md-sal/pom.xml b/opendaylight/md-sal/pom.xml index c0587652c0..bf0a082362 100644 --- a/opendaylight/md-sal/pom.xml +++ b/opendaylight/md-sal/pom.xml @@ -56,6 +56,7 @@ sal-rest-docgen-maven sal-akka-raft + sal-akka-raft-example sal-inmemory-datastore diff --git a/opendaylight/md-sal/sal-akka-raft-example/pom.xml b/opendaylight/md-sal/sal-akka-raft-example/pom.xml new file mode 100644 index 0000000000..46fae5533e --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft-example/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + org.opendaylight.controller + sal-parent + 1.2.0-SNAPSHOT + + sal-akka-raft-example + bundle + + + + org.opendaylight.controller + sal-akka-raft + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.groupId}.${project.artifactId} + + + + + + + scm:git:ssh://git.opendaylight.org:29418/controller.git + scm:git:ssh://git.opendaylight.org:29418/controller.git + HEAD + https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL:Architecture:Clustering + + diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ClientActor.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ClientActor.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ClientActor.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ClientActor.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ExampleActor.java similarity index 86% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ExampleActor.java index ed19f21ded..5ab3f69bea 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java +++ b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ExampleActor.java @@ -19,6 +19,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Map; +import javax.annotation.Nonnull; import org.opendaylight.controller.cluster.example.messages.KeyValue; import org.opendaylight.controller.cluster.example.messages.KeyValueSaved; import org.opendaylight.controller.cluster.example.messages.PrintRole; @@ -26,6 +27,8 @@ import org.opendaylight.controller.cluster.example.messages.PrintState; import org.opendaylight.controller.cluster.notifications.RoleChangeNotifier; import org.opendaylight.controller.cluster.raft.ConfigParams; import org.opendaylight.controller.cluster.raft.RaftActor; +import org.opendaylight.controller.cluster.raft.RaftActorRecoveryCohort; +import org.opendaylight.controller.cluster.raft.RaftActorSnapshotCohort; import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; import org.opendaylight.controller.cluster.raft.behaviors.Leader; @@ -34,9 +37,9 @@ import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payloa /** * A sample actor showing how the RaftActor is to be extended */ -public class ExampleActor extends RaftActor { +public class ExampleActor extends RaftActor implements RaftActorRecoveryCohort, RaftActorSnapshotCohort { - private final Map state = new HashMap(); + private final Map state = new HashMap<>(); private long persistIdentifier = 1; private final Optional roleChangeNotifier; @@ -118,7 +121,8 @@ public class ExampleActor extends RaftActor { } } - @Override protected void createSnapshot() { + @Override + public void createSnapshot(ActorRef actorRef) { ByteString bs = null; try { bs = fromObject(state); @@ -128,15 +132,16 @@ public class ExampleActor extends RaftActor { getSelf().tell(new CaptureSnapshotReply(bs.toByteArray()), null); } - @Override protected void applySnapshot(byte [] snapshot) { + @Override + public void applySnapshot(byte [] snapshot) { state.clear(); try { - state.putAll((HashMap) toObject(snapshot)); + state.putAll((HashMap) toObject(snapshot)); } catch (Exception e) { LOG.error("Exception in applying snapshot", e); } if(LOG.isDebugEnabled()) { - LOG.debug("Snapshot applied to state : {}", ((HashMap) state).size()); + LOG.debug("Snapshot applied to state : {}", ((HashMap) state).size()); } } @@ -192,22 +197,33 @@ public class ExampleActor extends RaftActor { } @Override - protected void startLogRecoveryBatch(int maxBatchSize) { + @Nonnull + protected RaftActorRecoveryCohort getRaftActorRecoveryCohort() { + return this; + } + + @Override + public void startLogRecoveryBatch(int maxBatchSize) { + } + + @Override + public void appendRecoveredLogEntry(Payload data) { } @Override - protected void appendRecoveredLogEntry(Payload data) { + public void applyCurrentLogRecoveryBatch() { } @Override - protected void applyCurrentLogRecoveryBatch() { + public void onRecoveryComplete() { } @Override - protected void onRecoveryComplete() { + public void applyRecoverySnapshot(byte[] snapshot) { } @Override - protected void applyRecoverySnapshot(byte[] snapshot) { + protected RaftActorSnapshotCohort getRaftActorSnapshotCohort() { + return this; } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleConfigParamsImpl.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ExampleConfigParamsImpl.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleConfigParamsImpl.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ExampleConfigParamsImpl.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleRoleChangeListener.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ExampleRoleChangeListener.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleRoleChangeListener.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/ExampleRoleChangeListener.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/LogGenerator.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/LogGenerator.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/LogGenerator.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/LogGenerator.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/Main.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/Main.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/Main.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/Main.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/TestDriver.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/TestDriver.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/TestDriver.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/TestDriver.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/KeyValue.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/KeyValue.java similarity index 92% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/KeyValue.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/KeyValue.java index d2862c2baf..2eb4189eac 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/KeyValue.java +++ b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/KeyValue.java @@ -53,8 +53,8 @@ public class KeyValue extends Payload implements Serializable { } // override this method to return the protobuff related extension fields and their values - @Override public Map encode() { - Map map = new HashMap<>(); + @Override public Map, String> encode() { + Map, String> map = new HashMap<>(); map.put(KeyValueMessages.key, getKey()); map.put(KeyValueMessages.value, getValue()); return map; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/KeyValueSaved.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/KeyValueSaved.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/KeyValueSaved.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/KeyValueSaved.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/PrintRole.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/PrintRole.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/PrintRole.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/PrintRole.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/PrintState.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/PrintState.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/PrintState.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/PrintState.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/RegisterListener.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/RegisterListener.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/RegisterListener.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/RegisterListener.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/SetNotifiers.java b/opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/SetNotifiers.java similarity index 100% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/messages/SetNotifiers.java rename to opendaylight/md-sal/sal-akka-raft-example/src/main/java/opendaylight/controller/cluster/example/messages/SetNotifiers.java diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImpl.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImpl.java index 1aecc89eea..b4b2afbc4a 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImpl.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImpl.java @@ -7,6 +7,7 @@ */ package org.opendaylight.controller.cluster.raft; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.Collections; @@ -19,7 +20,7 @@ import java.util.List; public abstract class AbstractReplicatedLogImpl implements ReplicatedLog { // We define this as ArrayList so we can use ensureCapacity. - protected ArrayList journal; + private ArrayList journal; private long snapshotIndex = -1; private long snapshotTerm = -1; @@ -28,13 +29,17 @@ public abstract class AbstractReplicatedLogImpl implements ReplicatedLog { private ArrayList snapshottedJournal; private long previousSnapshotIndex = -1; private long previousSnapshotTerm = -1; - protected int dataSize = 0; + private int dataSize = 0; public AbstractReplicatedLogImpl(long snapshotIndex, long snapshotTerm, List unAppliedEntries) { this.snapshotIndex = snapshotIndex; this.snapshotTerm = snapshotTerm; this.journal = new ArrayList<>(unAppliedEntries); + + for(ReplicatedLogEntry entry: journal) { + dataSize += entry.size(); + } } public AbstractReplicatedLogImpl() { @@ -90,18 +95,26 @@ public abstract class AbstractReplicatedLogImpl implements ReplicatedLog { } @Override - public void removeFrom(long logEntryIndex) { + public long removeFrom(long logEntryIndex) { int adjustedIndex = adjustedIndex(logEntryIndex); if (adjustedIndex < 0 || adjustedIndex >= journal.size()) { // physical index should be less than list size and >= 0 - return; + return -1; + } + + for(int i = adjustedIndex; i < journal.size(); i++) { + dataSize -= journal.get(i).size(); } + journal.subList(adjustedIndex , journal.size()).clear(); + + return adjustedIndex; } @Override public void append(ReplicatedLogEntry replicatedLogEntry) { journal.add(replicatedLogEntry); + dataSize += replicatedLogEntry.size(); } @Override @@ -196,7 +209,7 @@ public abstract class AbstractReplicatedLogImpl implements ReplicatedLog { List snapshotJournalEntries = journal.subList(0, (int) (snapshotCapturedIndex - snapshotIndex)); snapshottedJournal.addAll(snapshotJournalEntries); - clear(0, (int) (snapshotCapturedIndex - snapshotIndex)); + snapshotJournalEntries.clear(); previousSnapshotIndex = snapshotIndex; setSnapshotIndex(snapshotCapturedIndex); @@ -230,4 +243,9 @@ public abstract class AbstractReplicatedLogImpl implements ReplicatedLog { snapshotTerm = previousSnapshotTerm; previousSnapshotTerm = -1; } + + @VisibleForTesting + ReplicatedLogEntry getAtPhysicalIndex(int index) { + return journal.get(index); + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java index 1c30fe2317..157a53ed2d 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, @@ -11,15 +12,10 @@ package org.opendaylight.controller.cluster.raft; import akka.actor.ActorRef; import akka.actor.ActorSelection; import akka.japi.Procedure; -import akka.persistence.RecoveryCompleted; -import akka.persistence.SaveSnapshotFailure; -import akka.persistence.SaveSnapshotSuccess; -import akka.persistence.SnapshotOffer; import akka.persistence.SnapshotSelectionCriteria; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; import com.google.common.base.Optional; -import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import java.io.Serializable; @@ -27,6 +23,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; import org.apache.commons.lang3.time.DurationFormatUtils; import org.opendaylight.controller.cluster.DataPersistenceProvider; import org.opendaylight.controller.cluster.DelegatingPersistentDataProvider; @@ -36,11 +33,7 @@ import org.opendaylight.controller.cluster.common.actor.AbstractUntypedPersisten import org.opendaylight.controller.cluster.notifications.LeaderStateChanged; import org.opendaylight.controller.cluster.notifications.RoleChanged; import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntries; -import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; -import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; -import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; -import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; import org.opendaylight.controller.cluster.raft.base.messages.Replicate; import org.opendaylight.controller.cluster.raft.behaviors.AbstractLeader; import org.opendaylight.controller.cluster.raft.behaviors.DelegatingRaftActorBehavior; @@ -99,8 +92,6 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { private static final long APPLY_STATE_DELAY_THRESHOLD_IN_NANOS = TimeUnit.MILLISECONDS.toNanos(50L); // 50 millis - private static final String COMMIT_SNAPSHOT = "commit_snapshot"; - protected final Logger LOG = LoggerFactory.getLogger(getClass()); /** @@ -117,11 +108,9 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { private final DelegatingPersistentDataProvider delegatingPersistenceProvider = new DelegatingPersistentDataProvider(null); - private final Procedure createSnapshotProcedure = new CreateSnapshotProcedure(); - - private Stopwatch recoveryTimer; + private RaftActorRecoverySupport raftRecovery; - private int currentRecoveryBatchCount; + private RaftActorSnapshotMessageSupport snapshotSupport; private final BehaviorStateHolder reusableBehaviorStateHolder = new BehaviorStateHolder(); @@ -135,17 +124,12 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { context = new RaftActorContextImpl(this.getSelf(), this.getContext(), id, new ElectionTermImpl(delegatingPersistenceProvider, id, LOG), -1, -1, peerAddresses, - (configParams.isPresent() ? configParams.get(): new DefaultConfigParamsImpl()), LOG); + (configParams.isPresent() ? configParams.get(): new DefaultConfigParamsImpl()), + delegatingPersistenceProvider, LOG); context.setReplicatedLog(ReplicatedLogImpl.newInstance(context, delegatingPersistenceProvider, currentBehavior)); } - private void initRecoveryTimer() { - if(recoveryTimer == null) { - recoveryTimer = Stopwatch.createStarted(); - } - } - @Override public void preStart() throws Exception { LOG.info("Starting recovery for {} with journal batch size {}", persistenceId(), @@ -156,7 +140,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { @Override public void postStop() { - if(currentBehavior != null) { + if(currentBehavior.getDelegate() != null) { try { currentBehavior.close(); } catch (Exception e) { @@ -169,134 +153,32 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { @Override public void handleRecover(Object message) { - if(persistence().isRecoveryApplicable()) { - if (message instanceof SnapshotOffer) { - onRecoveredSnapshot((SnapshotOffer) message); - } else if (message instanceof ReplicatedLogEntry) { - onRecoveredJournalLogEntry((ReplicatedLogEntry) message); - } else if (message instanceof ApplyLogEntries) { - // Handle this message for backwards compatibility with pre-Lithium versions. - onRecoveredApplyLogEntries(((ApplyLogEntries) message).getToIndex()); - } else if (message instanceof ApplyJournalEntries) { - onRecoveredApplyLogEntries(((ApplyJournalEntries) message).getToIndex()); - } else if (message instanceof DeleteEntries) { - replicatedLog().removeFrom(((DeleteEntries) message).getFromIndex()); - } else if (message instanceof UpdateElectionTerm) { - context.getTermInformation().update(((UpdateElectionTerm) message).getCurrentTerm(), - ((UpdateElectionTerm) message).getVotedFor()); - } else if (message instanceof RecoveryCompleted) { - onRecoveryCompletedMessage(); - } - } else { - if (message instanceof RecoveryCompleted) { + if(raftRecovery == null) { + raftRecovery = newRaftActorRecoverySupport(); + } + + boolean recoveryComplete = raftRecovery.handleRecoveryMessage(message); + if(recoveryComplete) { + if(!persistence().isRecoveryApplicable()) { // Delete all the messages from the akka journal so that we do not end up with consistency issues // Note I am not using the dataPersistenceProvider and directly using the akka api here deleteMessages(lastSequenceNr()); // Delete all the akka snapshots as they will not be needed deleteSnapshots(new SnapshotSelectionCriteria(scala.Long.MaxValue(), scala.Long.MaxValue())); - - onRecoveryComplete(); - - initializeBehavior(); } - } - } - - private void onRecoveredSnapshot(SnapshotOffer offer) { - if(LOG.isDebugEnabled()) { - LOG.debug("{}: SnapshotOffer called..", persistenceId()); - } - - initRecoveryTimer(); - - Snapshot snapshot = (Snapshot) offer.snapshot(); - - // Create a replicated log with the snapshot information - // The replicated log can be used later on to retrieve this snapshot - // when we need to install it on a peer - - context.setReplicatedLog(ReplicatedLogImpl.newInstance(snapshot, context, delegatingPersistenceProvider, - currentBehavior)); - context.setLastApplied(snapshot.getLastAppliedIndex()); - context.setCommitIndex(snapshot.getLastAppliedIndex()); - - Stopwatch timer = Stopwatch.createStarted(); - - // Apply the snapshot to the actors state - applyRecoverySnapshot(snapshot.getState()); - - timer.stop(); - LOG.info("Recovery snapshot applied for {} in {}: snapshotIndex={}, snapshotTerm={}, journal-size=" + - replicatedLog().size(), persistenceId(), timer.toString(), - replicatedLog().getSnapshotIndex(), replicatedLog().getSnapshotTerm()); - } - private void onRecoveredJournalLogEntry(ReplicatedLogEntry logEntry) { - if(LOG.isDebugEnabled()) { - LOG.debug("{}: Received ReplicatedLogEntry for recovery: {}", persistenceId(), logEntry.getIndex()); - } - - replicatedLog().append(logEntry); - } - - private void onRecoveredApplyLogEntries(long toIndex) { - if(LOG.isDebugEnabled()) { - LOG.debug("{}: Received ApplyLogEntries for recovery, applying to state: {} to {}", - persistenceId(), context.getLastApplied() + 1, toIndex); - } - - for (long i = context.getLastApplied() + 1; i <= toIndex; i++) { - batchRecoveredLogEntry(replicatedLog().get(i)); - } - - context.setLastApplied(toIndex); - context.setCommitIndex(toIndex); - } - - private void batchRecoveredLogEntry(ReplicatedLogEntry logEntry) { - initRecoveryTimer(); + onRecoveryComplete(); - int batchSize = context.getConfigParams().getJournalRecoveryLogBatchSize(); - if(currentRecoveryBatchCount == 0) { - startLogRecoveryBatch(batchSize); - } - - appendRecoveredLogEntry(logEntry.getData()); + initializeBehavior(); - if(++currentRecoveryBatchCount >= batchSize) { - endCurrentLogRecoveryBatch(); + raftRecovery = null; } } - private void endCurrentLogRecoveryBatch() { - applyCurrentLogRecoveryBatch(); - currentRecoveryBatchCount = 0; - } - - private void onRecoveryCompletedMessage() { - if(currentRecoveryBatchCount > 0) { - endCurrentLogRecoveryBatch(); - } - - onRecoveryComplete(); - - String recoveryTime = ""; - if(recoveryTimer != null) { - recoveryTimer.stop(); - recoveryTime = " in " + recoveryTimer.toString(); - recoveryTimer = null; - } - - LOG.info( - "Recovery completed" + recoveryTime + " - Switching actor to Follower - " + - "Persistence Id = " + persistenceId() + - " Last index in log={}, snapshotIndex={}, snapshotTerm={}, " + - "journal-size={}", - replicatedLog().lastIndex(), replicatedLog().getSnapshotIndex(), - replicatedLog().getSnapshotTerm(), replicatedLog().size()); - - initializeBehavior(); + protected RaftActorRecoverySupport newRaftActorRecoverySupport() { + return new RaftActorRecoverySupport(delegatingPersistenceProvider, context, currentBehavior, + getRaftActorRecoveryCohort()); } protected void initializeBehavior(){ @@ -309,7 +191,17 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { handleBehaviorChange(reusableBehaviorStateHolder, getCurrentBehavior()); } - @Override public void handleCommand(Object message) { + @Override + public void handleCommand(Object message) { + if(snapshotSupport == null) { + snapshotSupport = newRaftActorSnapshotMessageSupport(); + } + + boolean handled = snapshotSupport.handleSnapshotMessage(message); + if(handled) { + return; + } + if (message instanceof ApplyState){ ApplyState applyState = (ApplyState) message; @@ -336,56 +228,13 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { persistence().persist(applyEntries, NoopProcedure.instance()); - } else if(message instanceof ApplySnapshot ) { - Snapshot snapshot = ((ApplySnapshot) message).getSnapshot(); - - if(LOG.isDebugEnabled()) { - LOG.debug("{}: ApplySnapshot called on Follower Actor " + - "snapshotIndex:{}, snapshotTerm:{}", persistenceId(), snapshot.getLastAppliedIndex(), - snapshot.getLastAppliedTerm() - ); - } - - applySnapshot(snapshot.getState()); - - //clears the followers log, sets the snapshot index to ensure adjusted-index works - context.setReplicatedLog(ReplicatedLogImpl.newInstance(snapshot, context, delegatingPersistenceProvider, - currentBehavior)); - context.setLastApplied(snapshot.getLastAppliedIndex()); - } else if (message instanceof FindLeader) { getSender().tell( new FindLeaderReply(getLeaderAddress()), getSelf() ); - - } else if (message instanceof SaveSnapshotSuccess) { - SaveSnapshotSuccess success = (SaveSnapshotSuccess) message; - LOG.info("{}: SaveSnapshotSuccess received for snapshot", persistenceId()); - - long sequenceNumber = success.metadata().sequenceNr(); - - commitSnapshot(sequenceNumber); - - } else if (message instanceof SaveSnapshotFailure) { - SaveSnapshotFailure saveSnapshotFailure = (SaveSnapshotFailure) message; - - LOG.error("{}: SaveSnapshotFailure received for snapshot Cause:", - persistenceId(), saveSnapshotFailure.cause()); - - context.getSnapshotManager().rollback(); - - } else if (message instanceof CaptureSnapshot) { - LOG.debug("{}: CaptureSnapshot received by actor: {}", persistenceId(), message); - - context.getSnapshotManager().create(createSnapshotProcedure); - - } else if (message instanceof CaptureSnapshotReply) { - handleCaptureSnapshotReply(((CaptureSnapshotReply) message).getSnapshot()); } else if(message instanceof GetOnDemandRaftState) { onGetOnDemandRaftStats(); - } else if (message.equals(COMMIT_SNAPSHOT)) { - commitSnapshot(-1); } else { reusableBehaviorStateHolder.init(getCurrentBehavior()); @@ -395,6 +244,11 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } } + protected RaftActorSnapshotMessageSupport newRaftActorSnapshotMessageSupport() { + return new RaftActorSnapshotMessageSupport(delegatingPersistenceProvider, context, + currentBehavior, getRaftActorSnapshotCohort()); + } + private void onGetOnDemandRaftStats() { // Debugging message to retrieve raft stats. @@ -621,7 +475,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { // Make saving Snapshot successful // Committing the snapshot here would end up calling commit in the creating state which would // be a state violation. That's why now we send a message to commit the snapshot. - self().tell(COMMIT_SNAPSHOT, self()); + self().tell(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT, self()); } }); } @@ -645,10 +499,6 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { context.setPeerAddress(peerId, peerAddress); } - protected void commitSnapshot(long sequenceNumber) { - context.getSnapshotManager().commit(persistence(), sequenceNumber); - } - /** * The applyState method will be called by the RaftActor when some data * needs to be applied to the actor's state @@ -670,31 +520,10 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { Object data); /** - * This method is called during recovery at the start of a batch of state entries. Derived - * classes should perform any initialization needed to start a batch. + * Returns the RaftActorRecoveryCohort to participate in persistence recovery. */ - protected abstract void startLogRecoveryBatch(int maxBatchSize); - - /** - * This method is called during recovery to append state data to the current batch. This method - * is called 1 or more times after {@link #startLogRecoveryBatch}. - * - * @param data the state data - */ - protected abstract void appendRecoveredLogEntry(Payload data); - - /** - * This method is called during recovery to reconstruct the state of the actor. - * - * @param snapshotBytes A snapshot of the state of the actor - */ - protected abstract void applyRecoverySnapshot(byte[] snapshotBytes); - - /** - * This method is called during recovery at the end of a batch to apply the current batched - * log entries. This method is called after {@link #appendRecoveredLogEntry}. - */ - protected abstract void applyCurrentLogRecoveryBatch(); + @Nonnull + protected abstract RaftActorRecoveryCohort getRaftActorRecoveryCohort(); /** * This method is called when recovery is complete. @@ -702,24 +531,10 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { protected abstract void onRecoveryComplete(); /** - * This method will be called by the RaftActor when a snapshot needs to be - * created. The derived actor should respond with its current state. - *

- * During recovery the state that is returned by the derived actor will - * be passed back to it by calling the applySnapshot method - * - * @return The current state of the actor + * Returns the RaftActorSnapshotCohort to participate in persistence recovery. */ - protected abstract void createSnapshot(); - - /** - * This method can be called at any other point during normal - * operations when the derived actor is out of sync with it's peers - * and the only way to bring it in sync is by applying a snapshot - * - * @param snapshotBytes A snapshot of the state of the actor - */ - protected abstract void applySnapshot(byte[] snapshotBytes); + @Nonnull + protected abstract RaftActorSnapshotCohort getRaftActorSnapshotCohort(); /** * This method will be called by the RaftActor when the state of the @@ -753,16 +568,16 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { return peerAddress; } - private void handleCaptureSnapshotReply(byte[] snapshotBytes) { - LOG.debug("{}: CaptureSnapshotReply received by actor: snapshot size {}", persistenceId(), snapshotBytes.length); - - context.getSnapshotManager().persist(persistence(), snapshotBytes, currentBehavior, context.getTotalMemory()); - } - protected boolean hasFollowers(){ return getRaftActorContext().hasFollowers(); } + /** + * @deprecated Deprecated in favor of {@link org.opendaylight.controller.cluster.raft.base.messages.DeleteEntriesTest} + * whose type for fromIndex is long instead of int. This class was kept for backwards + * compatibility with Helium. + */ + @Deprecated static class DeleteEntries implements Serializable { private static final long serialVersionUID = 1L; private final int fromIndex; @@ -795,14 +610,6 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } } - private class CreateSnapshotProcedure implements Procedure { - - @Override - public void apply(Void aVoid) throws Exception { - createSnapshot(); - } - } - private static class BehaviorStateHolder { private RaftActorBehavior behavior; private String leaderId; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContext.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContext.java index 9f4b7cb482..7198876ca6 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContext.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContext.java @@ -15,6 +15,7 @@ import akka.actor.Props; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; import java.util.Map; +import org.opendaylight.controller.cluster.DataPersistenceProvider; import org.slf4j.Logger; /** @@ -170,6 +171,8 @@ public interface RaftActorContext { SnapshotManager getSnapshotManager(); + DataPersistenceProvider getPersistenceProvider(); + boolean hasFollowers(); long getTotalMemory(); diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContextImpl.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContextImpl.java index 684845c270..049b91c416 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContextImpl.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContextImpl.java @@ -17,6 +17,7 @@ import akka.actor.UntypedActorContext; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Supplier; import java.util.Map; +import org.opendaylight.controller.cluster.DataPersistenceProvider; import org.slf4j.Logger; public class RaftActorContextImpl implements RaftActorContext { @@ -48,9 +49,11 @@ public class RaftActorContextImpl implements RaftActorContext { // be passed to it in the constructor private SnapshotManager snapshotManager; + private final DataPersistenceProvider persistenceProvider; + public RaftActorContextImpl(ActorRef actor, UntypedActorContext context, String id, ElectionTerm termInformation, long commitIndex, long lastApplied, Map peerAddresses, - ConfigParams configParams, Logger logger) { + ConfigParams configParams, DataPersistenceProvider persistenceProvider, Logger logger) { this.actor = actor; this.context = context; this.id = id; @@ -59,6 +62,7 @@ public class RaftActorContextImpl implements RaftActorContext { this.lastApplied = lastApplied; this.peerAddresses = peerAddresses; this.configParams = configParams; + this.persistenceProvider = persistenceProvider; this.LOG = logger; } @@ -182,4 +186,9 @@ public class RaftActorContextImpl implements RaftActorContext { public boolean hasFollowers() { return getPeerAddresses().keySet().size() > 0; } + + @Override + public DataPersistenceProvider getPersistenceProvider() { + return persistenceProvider; + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorRecoveryCohort.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorRecoveryCohort.java new file mode 100644 index 0000000000..a9f00aa80b --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorRecoveryCohort.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; + +/** + * Interface for a class that participates in raft actor persistence recovery. + * + * @author Thomas Pantelis + */ +public interface RaftActorRecoveryCohort { + + /** + * This method is called during recovery at the start of a batch of state entries. Derived + * classes should perform any initialization needed to start a batch. + */ + void startLogRecoveryBatch(int maxBatchSize); + + /** + * This method is called during recovery to append state data to the current batch. This method + * is called 1 or more times after {@link #startLogRecoveryBatch}. + * + * @param data the state data + */ + void appendRecoveredLogEntry(Payload data); + + /** + * This method is called during recovery to reconstruct the state of the actor. + * + * @param snapshotBytes A snapshot of the state of the actor + */ + void applyRecoverySnapshot(byte[] snapshotBytes); + + /** + * This method is called during recovery at the end of a batch to apply the current batched + * log entries. This method is called after {@link #appendRecoveredLogEntry}. + */ + void applyCurrentLogRecoveryBatch(); +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorRecoverySupport.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorRecoverySupport.java new file mode 100644 index 0000000000..57603a5058 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorRecoverySupport.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import akka.persistence.RecoveryCompleted; +import akka.persistence.SnapshotOffer; +import com.google.common.base.Stopwatch; +import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.opendaylight.controller.cluster.raft.RaftActor.UpdateElectionTerm; +import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntries; +import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; +import org.opendaylight.controller.cluster.raft.base.messages.DeleteEntries; +import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; +import org.slf4j.Logger; + +/** + * Support class that handles persistence recovery for a RaftActor. + * + * @author Thomas Pantelis + */ +class RaftActorRecoverySupport { + private final DataPersistenceProvider persistence; + private final RaftActorContext context; + private final RaftActorBehavior currentBehavior; + private final RaftActorRecoveryCohort cohort; + + private int currentRecoveryBatchCount; + + private Stopwatch recoveryTimer; + private final Logger log; + + RaftActorRecoverySupport(DataPersistenceProvider persistence, RaftActorContext context, + RaftActorBehavior currentBehavior, RaftActorRecoveryCohort cohort) { + this.persistence = persistence; + this.context = context; + this.currentBehavior = currentBehavior; + this.cohort = cohort; + this.log = context.getLogger(); + } + + boolean handleRecoveryMessage(Object message) { + boolean recoveryComplete = false; + if(persistence.isRecoveryApplicable()) { + if (message instanceof SnapshotOffer) { + onRecoveredSnapshot((SnapshotOffer) message); + } else if (message instanceof ReplicatedLogEntry) { + onRecoveredJournalLogEntry((ReplicatedLogEntry) message); + } else if (message instanceof ApplyLogEntries) { + // Handle this message for backwards compatibility with pre-Lithium versions. + onRecoveredApplyLogEntries(((ApplyLogEntries) message).getToIndex()); + } else if (message instanceof ApplyJournalEntries) { + onRecoveredApplyLogEntries(((ApplyJournalEntries) message).getToIndex()); + } else if (message instanceof DeleteEntries) { + replicatedLog().removeFrom(((DeleteEntries) message).getFromIndex()); + } else if (message instanceof org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries) { + // Handle this message for backwards compatibility with pre-Lithium versions. + replicatedLog().removeFrom(((org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries) message).getFromIndex()); + } else if (message instanceof UpdateElectionTerm) { + context.getTermInformation().update(((UpdateElectionTerm) message).getCurrentTerm(), + ((UpdateElectionTerm) message).getVotedFor()); + } else if (message instanceof RecoveryCompleted) { + onRecoveryCompletedMessage(); + recoveryComplete = true; + } + } else if (message instanceof RecoveryCompleted) { + recoveryComplete = true; + } + + return recoveryComplete; + } + + private ReplicatedLog replicatedLog() { + return context.getReplicatedLog(); + } + + private void initRecoveryTimer() { + if(recoveryTimer == null) { + recoveryTimer = Stopwatch.createStarted(); + } + } + + private void onRecoveredSnapshot(SnapshotOffer offer) { + if(log.isDebugEnabled()) { + log.debug("{}: SnapshotOffer called..", context.getId()); + } + + initRecoveryTimer(); + + Snapshot snapshot = (Snapshot) offer.snapshot(); + + // Create a replicated log with the snapshot information + // The replicated log can be used later on to retrieve this snapshot + // when we need to install it on a peer + + context.setReplicatedLog(ReplicatedLogImpl.newInstance(snapshot, context, persistence, currentBehavior)); + context.setLastApplied(snapshot.getLastAppliedIndex()); + context.setCommitIndex(snapshot.getLastAppliedIndex()); + + Stopwatch timer = Stopwatch.createStarted(); + + // Apply the snapshot to the actors state + cohort.applyRecoverySnapshot(snapshot.getState()); + + timer.stop(); + log.info("Recovery snapshot applied for {} in {}: snapshotIndex={}, snapshotTerm={}, journal-size={}", + context.getId(), timer.toString(), replicatedLog().getSnapshotIndex(), + replicatedLog().getSnapshotTerm(), replicatedLog().size()); + } + + private void onRecoveredJournalLogEntry(ReplicatedLogEntry logEntry) { + if(log.isDebugEnabled()) { + log.debug("{}: Received ReplicatedLogEntry for recovery: index: {}, size: {}", context.getId(), + logEntry.getIndex(), logEntry.size()); + } + + replicatedLog().append(logEntry); + } + + private void onRecoveredApplyLogEntries(long toIndex) { + long lastUnappliedIndex = context.getLastApplied() + 1; + + if(log.isDebugEnabled()) { + log.debug("{}: Received apply journal entries for recovery, applying to state: {} to {}", + context.getId(), lastUnappliedIndex, toIndex); + } + + long lastApplied = lastUnappliedIndex - 1; + for (long i = lastUnappliedIndex; i <= toIndex; i++) { + ReplicatedLogEntry logEntry = replicatedLog().get(i); + if(logEntry != null) { + lastApplied++; + batchRecoveredLogEntry(logEntry); + } else { + // Shouldn't happen but cover it anyway. + log.error("Log entry not found for index {}", i); + break; + } + } + + context.setLastApplied(lastApplied); + context.setCommitIndex(lastApplied); + } + + private void batchRecoveredLogEntry(ReplicatedLogEntry logEntry) { + initRecoveryTimer(); + + int batchSize = context.getConfigParams().getJournalRecoveryLogBatchSize(); + if(currentRecoveryBatchCount == 0) { + cohort.startLogRecoveryBatch(batchSize); + } + + cohort.appendRecoveredLogEntry(logEntry.getData()); + + if(++currentRecoveryBatchCount >= batchSize) { + endCurrentLogRecoveryBatch(); + } + } + + private void endCurrentLogRecoveryBatch() { + cohort.applyCurrentLogRecoveryBatch(); + currentRecoveryBatchCount = 0; + } + + private void onRecoveryCompletedMessage() { + if(currentRecoveryBatchCount > 0) { + endCurrentLogRecoveryBatch(); + } + + String recoveryTime = ""; + if(recoveryTimer != null) { + recoveryTimer.stop(); + recoveryTime = " in " + recoveryTimer.toString(); + recoveryTimer = null; + } + + log.info("Recovery completed" + recoveryTime + " - Switching actor to Follower - " + + "Persistence Id = " + context.getId() + + " Last index in log = {}, snapshotIndex = {}, snapshotTerm = {}, " + + "journal-size = {}", replicatedLog().lastIndex(), replicatedLog().getSnapshotIndex(), + replicatedLog().getSnapshotTerm(), replicatedLog().size()); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotCohort.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotCohort.java new file mode 100644 index 0000000000..ad68726371 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotCohort.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import akka.actor.ActorRef; + +/** + * Interface for a class that participates in raft actor snapshotting. + * + * @author Thomas Pantelis + */ +public interface RaftActorSnapshotCohort { + + /** + * This method is called by the RaftActor when a snapshot needs to be + * created. The implementation should send a CaptureSnapshotReply to the given actor. + * + * @param actorRef the actor to which to respond + */ + void createSnapshot(ActorRef actorRef); + + /** + * This method is called to apply a snapshot installed by the leader. + * + * @param snapshotBytes a snapshot of the state of the actor + */ + void applySnapshot(byte[] snapshotBytes); +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotMessageSupport.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotMessageSupport.java new file mode 100644 index 0000000000..790ff89510 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotMessageSupport.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import akka.japi.Procedure; +import akka.persistence.SaveSnapshotFailure; +import akka.persistence.SaveSnapshotSuccess; +import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; +import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; +import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; +import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; +import org.slf4j.Logger; + +/** + * Handles snapshot related messages for a RaftActor. + * + * @author Thomas Pantelis + */ +class RaftActorSnapshotMessageSupport { + static final String COMMIT_SNAPSHOT = "commit_snapshot"; + + private final DataPersistenceProvider persistence; + private final RaftActorContext context; + private final RaftActorBehavior currentBehavior; + private final RaftActorSnapshotCohort cohort; + private final Logger log; + + private final Procedure createSnapshotProcedure = new Procedure() { + @Override + public void apply(Void notUsed) throws Exception { + cohort.createSnapshot(context.getActor()); + } + }; + + RaftActorSnapshotMessageSupport(DataPersistenceProvider persistence, RaftActorContext context, + RaftActorBehavior currentBehavior, RaftActorSnapshotCohort cohort) { + this.persistence = persistence; + this.context = context; + this.currentBehavior = currentBehavior; + this.cohort = cohort; + this.log = context.getLogger(); + } + + boolean handleSnapshotMessage(Object message) { + if(message instanceof ApplySnapshot ) { + onApplySnapshot(((ApplySnapshot) message).getSnapshot()); + return true; + } else if (message instanceof SaveSnapshotSuccess) { + onSaveSnapshotSuccess((SaveSnapshotSuccess) message); + return true; + } else if (message instanceof SaveSnapshotFailure) { + onSaveSnapshotFailure((SaveSnapshotFailure) message); + return true; + } else if (message instanceof CaptureSnapshot) { + onCaptureSnapshot(message); + return true; + } else if (message instanceof CaptureSnapshotReply) { + onCaptureSnapshotReply(((CaptureSnapshotReply) message).getSnapshot()); + return true; + } else if (message.equals(COMMIT_SNAPSHOT)) { + context.getSnapshotManager().commit(persistence, -1); + return true; + } else { + return false; + } + } + + private void onCaptureSnapshotReply(byte[] snapshotBytes) { + log.debug("{}: CaptureSnapshotReply received by actor: snapshot size {}", context.getId(), snapshotBytes.length); + + context.getSnapshotManager().persist(persistence, snapshotBytes, currentBehavior, context.getTotalMemory()); + } + + private void onCaptureSnapshot(Object message) { + log.debug("{}: CaptureSnapshot received by actor: {}", context.getId(), message); + + context.getSnapshotManager().create(createSnapshotProcedure); + } + + private void onSaveSnapshotFailure(SaveSnapshotFailure saveSnapshotFailure) { + log.error("{}: SaveSnapshotFailure received for snapshot Cause:", + context.getId(), saveSnapshotFailure.cause()); + + context.getSnapshotManager().rollback(); + } + + private void onSaveSnapshotSuccess(SaveSnapshotSuccess success) { + log.info("{}: SaveSnapshotSuccess received for snapshot", context.getId()); + + long sequenceNumber = success.metadata().sequenceNr(); + + context.getSnapshotManager().commit(persistence, sequenceNumber); + } + + private void onApplySnapshot(Snapshot snapshot) { + if(log.isDebugEnabled()) { + log.debug("{}: ApplySnapshot called on Follower Actor " + + "snapshotIndex:{}, snapshotTerm:{}", context.getId(), snapshot.getLastAppliedIndex(), + snapshot.getLastAppliedTerm()); + } + + cohort.applySnapshot(snapshot.getState()); + + //clears the followers log, sets the snapshot index to ensure adjusted-index works + context.setReplicatedLog(ReplicatedLogImpl.newInstance(snapshot, context, persistence, + currentBehavior)); + context.setLastApplied(snapshot.getLastAppliedIndex()); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLog.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLog.java index 3e4d727c71..8388eaf743 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLog.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLog.java @@ -51,8 +51,9 @@ public interface ReplicatedLog { * information * * @param index the index of the log entry + * @return the adjusted index of the first log entry removed or -1 if log entry not found. */ - void removeFrom(long index); + long removeFrom(long index); /** diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLogImpl.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLogImpl.java index fdb6305381..1cfe153869 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLogImpl.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLogImpl.java @@ -11,7 +11,7 @@ import akka.japi.Procedure; import java.util.Collections; import java.util.List; import org.opendaylight.controller.cluster.DataPersistenceProvider; -import org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries; +import org.opendaylight.controller.cluster.raft.base.messages.DeleteEntries; import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; /** @@ -27,11 +27,7 @@ class ReplicatedLogImpl extends AbstractReplicatedLogImpl { private final Procedure deleteProcedure = new Procedure() { @Override - public void apply(DeleteEntries param) { - dataSize = 0; - for (ReplicatedLogEntry entry : journal) { - dataSize += entry.size(); - } + public void apply(DeleteEntries notUsed) { } }; @@ -57,16 +53,11 @@ class ReplicatedLogImpl extends AbstractReplicatedLogImpl { @Override public void removeFromAndPersist(long logEntryIndex) { - int adjustedIndex = adjustedIndex(logEntryIndex); - - if (adjustedIndex < 0) { - return; - } - // FIXME: Maybe this should be done after the command is saved - journal.subList(adjustedIndex , journal.size()).clear(); - - persistence.persist(new DeleteEntries(adjustedIndex), deleteProcedure); + long adjustedIndex = removeFrom(logEntryIndex); + if(adjustedIndex >= 0) { + persistence.persist(new DeleteEntries(adjustedIndex), deleteProcedure); + } } @Override @@ -83,7 +74,7 @@ class ReplicatedLogImpl extends AbstractReplicatedLogImpl { } // FIXME : By adding the replicated log entry to the in-memory journal we are not truly ensuring durability of the logs - journal.add(replicatedLogEntry); + append(replicatedLogEntry); // When persisting events with persist it is guaranteed that the // persistent actor will not receive further commands between the @@ -96,8 +87,7 @@ class ReplicatedLogImpl extends AbstractReplicatedLogImpl { public void apply(ReplicatedLogEntry evt) throws Exception { int logEntrySize = replicatedLogEntry.size(); - dataSize += logEntrySize; - long dataSizeForCheck = dataSize; + long dataSizeForCheck = dataSize(); dataSizeSinceLastSnapshot += logEntrySize; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/SnapshotManager.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/SnapshotManager.java index 8121f75191..f4f936bf16 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/SnapshotManager.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/SnapshotManager.java @@ -11,6 +11,7 @@ package org.opendaylight.controller.cluster.raft; import akka.japi.Procedure; import akka.persistence.SnapshotSelectionCriteria; import com.google.protobuf.ByteString; +import java.util.List; import org.opendaylight.controller.cluster.DataPersistenceProvider; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.SendInstallSnapshot; @@ -19,7 +20,6 @@ import org.slf4j.Logger; public class SnapshotManager implements SnapshotState { - private final SnapshotState IDLE = new Idle(); private final SnapshotState CAPTURING = new Capturing(); private final SnapshotState PERSISTING = new Persisting(); @@ -35,6 +35,7 @@ public class SnapshotManager implements SnapshotState { private SnapshotState currentState = IDLE; private CaptureSnapshot captureSnapshot; + private long lastSequenceNumber = -1; public SnapshotManager(RaftActorContext context, Logger logger) { this.context = context; @@ -184,19 +185,26 @@ public class SnapshotManager implements SnapshotState { long newReplicatedToAllTerm = replicatedToAllTermInfoReader.getTerm(); // send a CaptureSnapshot to self to make the expensive operation async. + + List unAppliedEntries = context.getReplicatedLog().getFrom(lastAppliedIndex + 1); + captureSnapshot = new CaptureSnapshot(lastLogEntry.getIndex(), lastLogEntry.getTerm(), lastAppliedIndex, lastAppliedTerm, - newReplicatedToAllIndex, newReplicatedToAllTerm, targetFollower!=null); + newReplicatedToAllIndex, newReplicatedToAllTerm, unAppliedEntries, targetFollower != null); SnapshotManager.this.currentState = CAPTURING; - if(targetFollower != null){ - LOG.info("{}: Initiating snapshot capture {}", persistenceId(), captureSnapshot); - } else { + if(captureSnapshot.isInstallSnapshotInitiated()) { LOG.info("{}: Initiating snapshot capture {} to install on {}", persistenceId(), captureSnapshot, targetFollower); + } else { + LOG.info("{}: Initiating snapshot capture {}", persistenceId(), captureSnapshot); } + lastSequenceNumber = context.getPersistenceProvider().getLastSequenceNumber(); + + LOG.debug("lastSequenceNumber prior to capture: {}", lastSequenceNumber); + context.getActor().tell(captureSnapshot, context.getActor()); return true; @@ -261,7 +269,7 @@ public class SnapshotManager implements SnapshotState { // when snapshot is saved async, SaveSnapshotSuccess is raised. Snapshot sn = Snapshot.create(snapshotBytes, - context.getReplicatedLog().getFrom(captureSnapshot.getLastAppliedIndex() + 1), + captureSnapshot.getUnAppliedEntries(), captureSnapshot.getLastIndex(), captureSnapshot.getLastTerm(), captureSnapshot.getLastAppliedIndex(), captureSnapshot.getLastAppliedTerm()); @@ -336,8 +344,9 @@ public class SnapshotManager implements SnapshotState { persistenceProvider.deleteSnapshots(new SnapshotSelectionCriteria( sequenceNumber - context.getConfigParams().getSnapshotBatchCount(), 43200000)); - persistenceProvider.deleteMessages(sequenceNumber); + persistenceProvider.deleteMessages(lastSequenceNumber); + lastSequenceNumber = -1; SnapshotManager.this.currentState = IDLE; } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CaptureSnapshot.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CaptureSnapshot.java index daa8f7768a..7c182f04e4 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CaptureSnapshot.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CaptureSnapshot.java @@ -8,6 +8,10 @@ package org.opendaylight.controller.cluster.raft.base.messages; +import java.util.Collections; +import java.util.List; +import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; + public class CaptureSnapshot { private final long lastAppliedIndex; private final long lastAppliedTerm; @@ -16,14 +20,17 @@ public class CaptureSnapshot { private final boolean installSnapshotInitiated; private final long replicatedToAllIndex; private final long replicatedToAllTerm; + private final List unAppliedEntries; - public CaptureSnapshot(long lastIndex, long lastTerm, - long lastAppliedIndex, long lastAppliedTerm, long replicatedToAllIndex, long replicatedToAllTerm) { - this(lastIndex, lastTerm, lastAppliedIndex, lastAppliedTerm, replicatedToAllIndex , replicatedToAllTerm, false); + public CaptureSnapshot(long lastIndex, long lastTerm, long lastAppliedIndex, long lastAppliedTerm, + long replicatedToAllIndex, long replicatedToAllTerm, List unAppliedEntries) { + this(lastIndex, lastTerm, lastAppliedIndex, lastAppliedTerm, replicatedToAllIndex, replicatedToAllTerm, + unAppliedEntries, false); } - public CaptureSnapshot(long lastIndex, long lastTerm,long lastAppliedIndex, - long lastAppliedTerm, long replicatedToAllIndex, long replicatedToAllTerm, boolean installSnapshotInitiated) { + public CaptureSnapshot(long lastIndex, long lastTerm, long lastAppliedIndex, + long lastAppliedTerm, long replicatedToAllIndex, long replicatedToAllTerm, + List unAppliedEntries, boolean installSnapshotInitiated) { this.lastIndex = lastIndex; this.lastTerm = lastTerm; this.lastAppliedIndex = lastAppliedIndex; @@ -31,6 +38,7 @@ public class CaptureSnapshot { this.installSnapshotInitiated = installSnapshotInitiated; this.replicatedToAllIndex = replicatedToAllIndex; this.replicatedToAllTerm = replicatedToAllTerm; + this.unAppliedEntries = unAppliedEntries != null ? unAppliedEntries : Collections.emptyList(); } public long getLastAppliedIndex() { @@ -61,6 +69,10 @@ public class CaptureSnapshot { return replicatedToAllTerm; } + public List getUnAppliedEntries() { + return unAppliedEntries; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -68,7 +80,9 @@ public class CaptureSnapshot { .append(lastAppliedTerm).append(", lastIndex=").append(lastIndex).append(", lastTerm=") .append(lastTerm).append(", installSnapshotInitiated=").append(installSnapshotInitiated) .append(", replicatedToAllIndex=").append(replicatedToAllIndex).append(", replicatedToAllTerm=") - .append(replicatedToAllTerm).append("]"); + .append(replicatedToAllTerm).append(", unAppliedEntries size=").append(unAppliedEntries.size()).append("]"); return builder.toString(); } + + } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/DeleteEntries.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/DeleteEntries.java new file mode 100644 index 0000000000..97742c0457 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/DeleteEntries.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft.base.messages; + +import java.io.Serializable; + +/** + * Internal message that is stored in the akka's persistent journal to delete journal entries. + * + * @author Thomas Pantelis + */ +public class DeleteEntries implements Serializable { + private static final long serialVersionUID = 1L; + + private final long fromIndex; + + public DeleteEntries(long fromIndex) { + this.fromIndex = fromIndex; + } + + public long getFromIndex() { + return fromIndex; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("DeleteEntries [fromIndex=").append(fromIndex).append("]"); + return builder.toString(); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java index 2c433f9007..bdfdd9b376 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java @@ -460,7 +460,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { long followerNextIndex = followerLogInformation.getNextIndex(); boolean isFollowerActive = followerLogInformation.isFollowerActive(); boolean sendAppendEntries = false; - List entries = Collections.EMPTY_LIST; + List entries = Collections.emptyList(); if (mapFollowerToSnapshot.get(followerId) != null) { // if install snapshot is in process , then sent next chunk if possible diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractRaftActorIntegrationTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractRaftActorIntegrationTest.java index b910313b09..977cf0ef5e 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractRaftActorIntegrationTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractRaftActorIntegrationTest.java @@ -7,7 +7,6 @@ */ package org.opendaylight.controller.cluster.raft; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import akka.actor.ActorRef; import akka.actor.PoisonPill; @@ -20,6 +19,7 @@ import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -27,7 +27,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.junit.After; import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload; -import org.opendaylight.controller.cluster.raft.RaftActorTest.MockRaftActor; import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntries; import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; @@ -52,7 +51,6 @@ public abstract class AbstractRaftActorIntegrationTest extends AbstractActorTest private final TestActorRef collectorActor; private final Map, Boolean> dropMessages = new ConcurrentHashMap<>(); - private volatile byte[] snapshot; private TestRaftActor(String id, Map peerAddresses, ConfigParams config, TestActorRef collectorActor) { @@ -112,20 +110,14 @@ public abstract class AbstractRaftActorIntegrationTest extends AbstractActorTest } @Override - protected void createSnapshot() { - if(snapshot != null) { - getSelf().tell(new CaptureSnapshotReply(snapshot), ActorRef.noSender()); + public void createSnapshot(ActorRef actorRef) { + try { + actorRef.tell(new CaptureSnapshotReply(RaftActorTest.fromObject(getState()).toByteArray()), actorRef); + } catch (Exception e) { + e.printStackTrace(); } } - @Override - protected void applyRecoverySnapshot(byte[] bytes) { - } - - void setSnapshot(byte[] snapshot) { - this.snapshot = snapshot; - } - public ActorRef collectorActor() { return collectorActor; } @@ -159,6 +151,8 @@ public abstract class AbstractRaftActorIntegrationTest extends AbstractActorTest protected long initialTerm = 5; protected long currentTerm; + protected List expSnapshotState = new ArrayList<>(); + @After public void tearDown() { InMemoryJournal.clear(); @@ -184,7 +178,7 @@ public abstract class AbstractRaftActorIntegrationTest extends AbstractActorTest } protected void waitUntilLeader(ActorRef actorRef) { - RaftActorTest.RaftActorTestKit.waitUntilLeader(actorRef); + RaftActorTestKit.waitUntilLeader(actorRef); } protected TestActorRef newTestRaftActor(String id, Map peerAddresses, @@ -216,13 +210,20 @@ public abstract class AbstractRaftActorIntegrationTest extends AbstractActorTest }); } + @SuppressWarnings("unchecked") protected void verifySnapshot(String prefix, Snapshot snapshot, long lastAppliedTerm, - int lastAppliedIndex, long lastTerm, long lastIndex, byte[] data) { + int lastAppliedIndex, long lastTerm, long lastIndex) + throws Exception { assertEquals(prefix + " Snapshot getLastAppliedTerm", lastAppliedTerm, snapshot.getLastAppliedTerm()); assertEquals(prefix + " Snapshot getLastAppliedIndex", lastAppliedIndex, snapshot.getLastAppliedIndex()); assertEquals(prefix + " Snapshot getLastTerm", lastTerm, snapshot.getLastTerm()); assertEquals(prefix + " Snapshot getLastIndex", lastIndex, snapshot.getLastIndex()); - assertArrayEquals(prefix + " Snapshot getState", data, snapshot.getState()); + + List actualState = (List)MockRaftActor.toObject(snapshot.getState()); + assertEquals(prefix + " Snapshot getState size", expSnapshotState.size(), actualState.size()); + for(int i = 0; i < expSnapshotState.size(); i++) { + assertEquals(prefix + " Snapshot state " + i, expSnapshotState.get(i), actualState.get(i)); + } } protected void verifyPersistedJournal(String persistenceId, List expJournal) { diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImplTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImplTest.java index 8fdb7ea226..d175289af5 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImplTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImplTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, @@ -15,7 +16,6 @@ import akka.japi.Procedure; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -40,12 +40,33 @@ public class AbstractReplicatedLogImplTest { } - @After - public void tearDown() { - replicatedLogImpl.journal.clear(); - replicatedLogImpl.setSnapshotIndex(-1); - replicatedLogImpl.setSnapshotTerm(-1); - replicatedLogImpl = null; + @Test + public void testEmptyLog() { + replicatedLogImpl = new MockAbstractReplicatedLogImpl(); + + assertEquals("size", 0, replicatedLogImpl.size()); + assertEquals("dataSize", 0, replicatedLogImpl.dataSize()); + assertEquals("getSnapshotIndex", -1, replicatedLogImpl.getSnapshotIndex()); + assertEquals("getSnapshotTerm", -1, replicatedLogImpl.getSnapshotTerm()); + assertEquals("lastIndex", -1, replicatedLogImpl.lastIndex()); + assertEquals("lastTerm", -1, replicatedLogImpl.lastTerm()); + assertEquals("isPresent", false, replicatedLogImpl.isPresent(0)); + assertEquals("isInSnapshot", false, replicatedLogImpl.isInSnapshot(0)); + Assert.assertNull("get(0)", replicatedLogImpl.get(0)); + Assert.assertNull("last", replicatedLogImpl.last()); + + List list = replicatedLogImpl.getFrom(0, 1); + assertEquals("getFrom size", 0, list.size()); + + assertEquals("removeFrom", -1, replicatedLogImpl.removeFrom(1)); + + replicatedLogImpl.setSnapshotIndex(2); + replicatedLogImpl.setSnapshotTerm(1); + + assertEquals("getSnapshotIndex", 2, replicatedLogImpl.getSnapshotIndex()); + assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm()); + assertEquals("lastIndex", 2, replicatedLogImpl.lastIndex()); + assertEquals("lastTerm", 1, replicatedLogImpl.lastTerm()); } @Test @@ -65,7 +86,7 @@ public class AbstractReplicatedLogImplTest { // now create a snapshot of 3 entries, with 1 unapplied entry left in the log // It removes the entries which have made it to snapshot // and updates the snapshot index and term - Map state = takeSnapshot(3); + takeSnapshot(3); // check the values after the snapshot. // each index value passed in the test is the logical index (log entry index) @@ -101,7 +122,7 @@ public class AbstractReplicatedLogImplTest { assertEquals(2, replicatedLogImpl.getFrom(6).size()); // take a second snapshot with 5 entries with 0 unapplied entries left in the log - state = takeSnapshot(5); + takeSnapshot(5); assertEquals(0, replicatedLogImpl.size()); assertNull(replicatedLogImpl.last()); @@ -142,24 +163,67 @@ public class AbstractReplicatedLogImplTest { replicatedLogImpl.snapshotPreCommit(-1, -1); assertEquals(8, replicatedLogImpl.size()); assertEquals(-1, replicatedLogImpl.getSnapshotIndex()); + assertEquals(-1, replicatedLogImpl.getSnapshotTerm()); - replicatedLogImpl.snapshotPreCommit(4, 3); + replicatedLogImpl.snapshotPreCommit(4, 2); assertEquals(3, replicatedLogImpl.size()); assertEquals(4, replicatedLogImpl.getSnapshotIndex()); + assertEquals(2, replicatedLogImpl.getSnapshotTerm()); replicatedLogImpl.snapshotPreCommit(6, 3); assertEquals(1, replicatedLogImpl.size()); assertEquals(6, replicatedLogImpl.getSnapshotIndex()); + assertEquals(3, replicatedLogImpl.getSnapshotTerm()); replicatedLogImpl.snapshotPreCommit(7, 3); assertEquals(0, replicatedLogImpl.size()); assertEquals(7, replicatedLogImpl.getSnapshotIndex()); + assertEquals(3, replicatedLogImpl.getSnapshotTerm()); //running it again on an empty list should not throw exception replicatedLogImpl.snapshotPreCommit(7, 3); assertEquals(0, replicatedLogImpl.size()); assertEquals(7, replicatedLogImpl.getSnapshotIndex()); + assertEquals(3, replicatedLogImpl.getSnapshotTerm()); + } + + @Test + public void testSnapshotCommit() { + + replicatedLogImpl.snapshotPreCommit(1, 1); + + replicatedLogImpl.snapshotCommit(); + + assertEquals("size", 2, replicatedLogImpl.size()); + assertEquals("dataSize", 2, replicatedLogImpl.dataSize()); + assertEquals("getSnapshotIndex", 1, replicatedLogImpl.getSnapshotIndex()); + assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm()); + assertEquals("lastIndex", 3, replicatedLogImpl.lastIndex()); + assertEquals("lastTerm", 2, replicatedLogImpl.lastTerm()); + + Assert.assertNull("get(0)", replicatedLogImpl.get(0)); + Assert.assertNull("get(1)", replicatedLogImpl.get(1)); + Assert.assertNotNull("get(2)", replicatedLogImpl.get(2)); + Assert.assertNotNull("get(3)", replicatedLogImpl.get(3)); + } + + @Test + public void testSnapshotRollback() { + + replicatedLogImpl.snapshotPreCommit(1, 1); + + assertEquals("size", 2, replicatedLogImpl.size()); + assertEquals("getSnapshotIndex", 1, replicatedLogImpl.getSnapshotIndex()); + assertEquals("getSnapshotTerm", 1, replicatedLogImpl.getSnapshotTerm()); + + replicatedLogImpl.snapshotRollback(); + assertEquals("size", 4, replicatedLogImpl.size()); + assertEquals("dataSize", 4, replicatedLogImpl.dataSize()); + assertEquals("getSnapshotIndex", -1, replicatedLogImpl.getSnapshotIndex()); + assertEquals("getSnapshotTerm", -1, replicatedLogImpl.getSnapshotTerm()); + Assert.assertNotNull("get(0)", replicatedLogImpl.get(0)); + Assert.assertNotNull("get(3)", replicatedLogImpl.get(3)); } @Test @@ -187,19 +251,45 @@ public class AbstractReplicatedLogImplTest { assertTrue(replicatedLogImpl.isPresent(5)); } + @Test + public void testRemoveFrom() { + + replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("E", 2))); + replicatedLogImpl.append(new MockReplicatedLogEntry(2, 5, new MockPayload("F", 3))); + + assertEquals("dataSize", 9, replicatedLogImpl.dataSize()); + + long adjusted = replicatedLogImpl.removeFrom(4); + assertEquals("removeFrom - adjusted", 4, adjusted); + assertEquals("size", 4, replicatedLogImpl.size()); + assertEquals("dataSize", 4, replicatedLogImpl.dataSize()); + + takeSnapshot(1); + + adjusted = replicatedLogImpl.removeFrom(2); + assertEquals("removeFrom - adjusted", 1, adjusted); + assertEquals("size", 1, replicatedLogImpl.size()); + assertEquals("dataSize", 1, replicatedLogImpl.dataSize()); + + assertEquals("removeFrom - adjusted", -1, replicatedLogImpl.removeFrom(0)); + assertEquals("removeFrom - adjusted", -1, replicatedLogImpl.removeFrom(100)); + } + // create a snapshot for test public Map takeSnapshot(final int numEntries) { Map map = new HashMap<>(numEntries); - List entries = replicatedLogImpl.getEntriesTill(numEntries); - for (ReplicatedLogEntry entry : entries) { + + long lastIndex = 0; + long lastTerm = 0; + for(int i = 0; i < numEntries; i++) { + ReplicatedLogEntry entry = replicatedLogImpl.getAtPhysicalIndex(i); map.put(entry.getIndex(), entry.getData().toString()); + lastIndex = entry.getIndex(); + lastTerm = entry.getTerm(); } - int term = (int) replicatedLogImpl.lastTerm(); - int lastIndex = (int) entries.get(entries.size() - 1).getIndex(); - entries.clear(); - replicatedLogImpl.setSnapshotTerm(term); - replicatedLogImpl.setSnapshotIndex(lastIndex); + replicatedLogImpl.snapshotPreCommit(lastIndex, lastTerm); + replicatedLogImpl.snapshotCommit(); return map; @@ -213,15 +303,6 @@ public class AbstractReplicatedLogImplTest { public void removeFromAndPersist(final long index) { } - @Override - public int dataSize() { - return -1; - } - - public List getEntriesTill(final int index) { - return journal.subList(0, index); - } - @Override public void appendAndPersist(ReplicatedLogEntry replicatedLogEntry, Procedure callback) { } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ElectionTermImplTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ElectionTermImplTest.java new file mode 100644 index 0000000000..da49718a41 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ElectionTermImplTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; +import akka.japi.Procedure; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.opendaylight.controller.cluster.raft.RaftActor.UpdateElectionTerm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Unit tests for ElectionTermImpl. + * + * @author Thomas Pantelis + */ +public class ElectionTermImplTest { + private static final Logger LOG = LoggerFactory.getLogger(RaftActorRecoverySupportTest.class); + + @Mock + private DataPersistenceProvider mockPersistence; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void testUpdateAndPersist() throws Exception { + ElectionTermImpl impl = new ElectionTermImpl(mockPersistence, "test", LOG); + + impl.updateAndPersist(10, "member-1"); + + assertEquals("getCurrentTerm", 10, impl.getCurrentTerm()); + assertEquals("getVotedFor", "member-1", impl.getVotedFor()); + + ArgumentCaptor message = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor procedure = ArgumentCaptor.forClass(Procedure.class); + verify(mockPersistence).persist(message.capture(), procedure.capture()); + + assertEquals("Message type", UpdateElectionTerm.class, message.getValue().getClass()); + UpdateElectionTerm update = (UpdateElectionTerm)message.getValue(); + assertEquals("getCurrentTerm", 10, update.getCurrentTerm()); + assertEquals("getVotedFor", "member-1", update.getVotedFor()); + + procedure.getValue().apply(null); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActor.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActor.java new file mode 100644 index 0000000000..586ca8cda0 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActor.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.japi.Creator; +import com.google.common.base.Optional; +import com.google.common.util.concurrent.Uninterruptibles; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; + +public class MockRaftActor extends RaftActor implements RaftActorRecoveryCohort, RaftActorSnapshotCohort { + + final RaftActor actorDelegate; + final RaftActorRecoveryCohort recoveryCohortDelegate; + final RaftActorSnapshotCohort snapshotCohortDelegate; + private final CountDownLatch recoveryComplete = new CountDownLatch(1); + private final List state; + private ActorRef roleChangeNotifier; + private final CountDownLatch initializeBehaviorComplete = new CountDownLatch(1); + private RaftActorRecoverySupport raftActorRecoverySupport; + private RaftActorSnapshotMessageSupport snapshotMessageSupport; + + public static final class MockRaftActorCreator implements Creator { + private static final long serialVersionUID = 1L; + private final Map peerAddresses; + private final String id; + private final Optional config; + private final DataPersistenceProvider dataPersistenceProvider; + private final ActorRef roleChangeNotifier; + private RaftActorSnapshotMessageSupport snapshotMessageSupport; + + private MockRaftActorCreator(Map peerAddresses, String id, + Optional config, DataPersistenceProvider dataPersistenceProvider, + ActorRef roleChangeNotifier) { + this.peerAddresses = peerAddresses; + this.id = id; + this.config = config; + this.dataPersistenceProvider = dataPersistenceProvider; + this.roleChangeNotifier = roleChangeNotifier; + } + + @Override + public MockRaftActor create() throws Exception { + MockRaftActor mockRaftActor = new MockRaftActor(id, peerAddresses, config, + dataPersistenceProvider); + mockRaftActor.roleChangeNotifier = this.roleChangeNotifier; + mockRaftActor.snapshotMessageSupport = snapshotMessageSupport; + return mockRaftActor; + } + } + + public MockRaftActor(String id, Map peerAddresses, Optional config, + DataPersistenceProvider dataPersistenceProvider) { + super(id, peerAddresses, config); + state = new ArrayList<>(); + this.actorDelegate = mock(RaftActor.class); + this.recoveryCohortDelegate = mock(RaftActorRecoveryCohort.class); + this.snapshotCohortDelegate = mock(RaftActorSnapshotCohort.class); + if(dataPersistenceProvider == null){ + setPersistence(true); + } else { + setPersistence(dataPersistenceProvider); + } + } + + public void setRaftActorRecoverySupport(RaftActorRecoverySupport support) { + raftActorRecoverySupport = support; + } + + @Override + public RaftActorRecoverySupport newRaftActorRecoverySupport() { + return raftActorRecoverySupport != null ? raftActorRecoverySupport : super.newRaftActorRecoverySupport(); + } + + @Override + protected RaftActorSnapshotMessageSupport newRaftActorSnapshotMessageSupport() { + return snapshotMessageSupport != null ? snapshotMessageSupport : super.newRaftActorSnapshotMessageSupport(); + } + + public void waitForRecoveryComplete() { + try { + assertEquals("Recovery complete", true, recoveryComplete.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void waitForInitializeBehaviorComplete() { + try { + assertEquals("Behavior initialized", true, initializeBehaviorComplete.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + public void waitUntilLeader(){ + for(int i = 0;i < 10; i++){ + if(isLeader()){ + break; + } + Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); + } + } + + public List getState() { + return state; + } + + public static Props props(final String id, final Map peerAddresses, + Optional config){ + return Props.create(new MockRaftActorCreator(peerAddresses, id, config, null, null)); + } + + public static Props props(final String id, final Map peerAddresses, + Optional config, RaftActorSnapshotMessageSupport snapshotMessageSupport){ + MockRaftActorCreator creator = new MockRaftActorCreator(peerAddresses, id, config, null, null); + creator.snapshotMessageSupport = snapshotMessageSupport; + return Props.create(creator); + } + + public static Props props(final String id, final Map peerAddresses, + Optional config, DataPersistenceProvider dataPersistenceProvider){ + return Props.create(new MockRaftActorCreator(peerAddresses, id, config, dataPersistenceProvider, null)); + } + + public static Props props(final String id, final Map peerAddresses, + Optional config, ActorRef roleChangeNotifier){ + return Props.create(new MockRaftActorCreator(peerAddresses, id, config, null, roleChangeNotifier)); + } + + public static Props props(final String id, final Map peerAddresses, + Optional config, ActorRef roleChangeNotifier, + DataPersistenceProvider dataPersistenceProvider){ + return Props.create(new MockRaftActorCreator(peerAddresses, id, config, dataPersistenceProvider, roleChangeNotifier)); + } + + @Override protected void applyState(ActorRef clientActor, String identifier, Object data) { + actorDelegate.applyState(clientActor, identifier, data); + LOG.info("{}: applyState called: {}", persistenceId(), data); + + state.add(data); + } + + @Override + @Nonnull + protected RaftActorRecoveryCohort getRaftActorRecoveryCohort() { + return this; + } + + @Override + protected RaftActorSnapshotCohort getRaftActorSnapshotCohort() { + return this; + } + + @Override + public void startLogRecoveryBatch(int maxBatchSize) { + } + + @Override + public void appendRecoveredLogEntry(Payload data) { + state.add(data); + } + + @Override + public void applyCurrentLogRecoveryBatch() { + } + + @Override + protected void onRecoveryComplete() { + actorDelegate.onRecoveryComplete(); + recoveryComplete.countDown(); + } + + @Override + protected void initializeBehavior() { + super.initializeBehavior(); + initializeBehaviorComplete.countDown(); + } + + @Override + public void applyRecoverySnapshot(byte[] bytes) { + recoveryCohortDelegate.applyRecoverySnapshot(bytes); + try { + Object data = toObject(bytes); + if (data instanceof List) { + state.addAll((List) data); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void createSnapshot(ActorRef actorRef) { + LOG.info("{}: createSnapshot called", persistenceId()); + snapshotCohortDelegate.createSnapshot(actorRef); + } + + @Override + public void applySnapshot(byte [] snapshot) { + LOG.info("{}: applySnapshot called", persistenceId()); + snapshotCohortDelegate.applySnapshot(snapshot); + } + + @Override + protected void onStateChanged() { + actorDelegate.onStateChanged(); + } + + @Override + protected Optional getRoleChangeNotifier() { + return Optional.fromNullable(roleChangeNotifier); + } + + @Override public String persistenceId() { + return this.getId(); + } + + public static Object toObject(byte[] bs) throws ClassNotFoundException, IOException { + Object obj = null; + ByteArrayInputStream bis = null; + ObjectInputStream ois = null; + try { + bis = new ByteArrayInputStream(bs); + ois = new ObjectInputStream(bis); + obj = ois.readObject(); + } finally { + if (bis != null) { + bis.close(); + } + if (ois != null) { + ois.close(); + } + } + return obj; + } + + public ReplicatedLog getReplicatedLog(){ + return this.getRaftActorContext().getReplicatedLog(); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActorContext.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActorContext.java index 63f0df2f8c..adf7778fe7 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActorContext.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActorContext.java @@ -19,6 +19,8 @@ import com.google.protobuf.GeneratedMessage; import java.io.Serializable; import java.util.HashMap; import java.util.Map; +import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.opendaylight.controller.cluster.NonPersistentDataProvider; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; import org.opendaylight.controller.protobuff.messages.cluster.raft.AppendEntriesMessages; import org.opendaylight.controller.protobuff.messages.cluster.raft.test.MockPayloadMessages; @@ -38,6 +40,7 @@ public class MockRaftActorContext implements RaftActorContext { private ConfigParams configParams; private boolean snapshotCaptureInitiated; private SnapshotManager snapshotManager; + private DataPersistenceProvider persistenceProvider = new NonPersistentDataProvider(); public MockRaftActorContext(){ electionTerm = new ElectionTerm() { @@ -219,6 +222,15 @@ public class MockRaftActorContext implements RaftActorContext { return getPeerAddresses().keySet().size() > 0; } + @Override + public DataPersistenceProvider getPersistenceProvider() { + return persistenceProvider; + } + + public void setPersistenceProvider(DataPersistenceProvider persistenceProvider) { + this.persistenceProvider = persistenceProvider; + } + public static class SimpleReplicatedLog extends AbstractReplicatedLogImpl { @Override public void appendAndPersist( ReplicatedLogEntry replicatedLogEntry) { @@ -266,8 +278,8 @@ public class MockRaftActorContext implements RaftActorContext { this.size = size; } - @Override public Map encode() { - Map map = new HashMap(); + @Override public Map, String> encode() { + Map, String> map = new HashMap<>(); map.put(MockPayloadMessages.value, value); return map; } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorRecoverySupportTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorRecoverySupportTest.java new file mode 100644 index 0000000000..c4e0ef8f5f --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorRecoverySupportTest.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import akka.persistence.RecoveryCompleted; +import akka.persistence.SnapshotMetadata; +import akka.persistence.SnapshotOffer; +import java.util.Arrays; +import java.util.Collections; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.opendaylight.controller.cluster.raft.RaftActor.UpdateElectionTerm; +import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntries; +import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; +import org.opendaylight.controller.cluster.raft.base.messages.DeleteEntries; +import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Unit tests for RaftActorRecoverySupport. + * + * @author Thomas Pantelis + */ +public class RaftActorRecoverySupportTest { + + private static final Logger LOG = LoggerFactory.getLogger(RaftActorRecoverySupportTest.class); + + @Mock + private DataPersistenceProvider mockPersistence; + + @Mock + private RaftActorBehavior mockBehavior; + + @Mock + private RaftActorRecoveryCohort mockCohort; + + private RaftActorRecoverySupport support; + + private RaftActorContext context; + private final DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + context = new RaftActorContextImpl(null, null, "test", new ElectionTermImpl(mockPersistence, "test", LOG), + -1, -1, Collections.emptyMap(), configParams, mockPersistence, LOG); + + support = new RaftActorRecoverySupport(mockPersistence, context , mockBehavior, mockCohort); + + doReturn(true).when(mockPersistence).isRecoveryApplicable(); + + context.setReplicatedLog(ReplicatedLogImpl.newInstance(context, mockPersistence, mockBehavior)); + } + + private void sendMessageToSupport(Object message) { + sendMessageToSupport(message, false); + } + + private void sendMessageToSupport(Object message, boolean expComplete) { + boolean complete = support.handleRecoveryMessage(message); + assertEquals("complete", expComplete, complete); + } + + @Test + public void testOnReplicatedLogEntry() { + MockRaftActorContext.MockReplicatedLogEntry logEntry = new MockRaftActorContext.MockReplicatedLogEntry(1, + 1, new MockRaftActorContext.MockPayload("1", 5)); + + sendMessageToSupport(logEntry); + + assertEquals("Journal log size", 1, context.getReplicatedLog().size()); + assertEquals("Journal data size", 5, context.getReplicatedLog().dataSize()); + assertEquals("Last index", 1, context.getReplicatedLog().lastIndex()); + assertEquals("Last applied", -1, context.getLastApplied()); + assertEquals("Commit index", -1, context.getCommitIndex()); + assertEquals("Snapshot term", -1, context.getReplicatedLog().getSnapshotTerm()); + assertEquals("Snapshot index", -1, context.getReplicatedLog().getSnapshotIndex()); + } + + @Test + public void testOnApplyJournalEntries() { + configParams.setJournalRecoveryLogBatchSize(5); + + ReplicatedLog replicatedLog = context.getReplicatedLog(); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 0, new MockRaftActorContext.MockPayload("0"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 1, new MockRaftActorContext.MockPayload("1"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 2, new MockRaftActorContext.MockPayload("2"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 3, new MockRaftActorContext.MockPayload("3"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 4, new MockRaftActorContext.MockPayload("4"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 5, new MockRaftActorContext.MockPayload("5"))); + + sendMessageToSupport(new ApplyJournalEntries(2)); + + assertEquals("Last applied", 2, context.getLastApplied()); + assertEquals("Commit index", 2, context.getCommitIndex()); + + sendMessageToSupport(new ApplyJournalEntries(4)); + + assertEquals("Last applied", 4, context.getLastApplied()); + assertEquals("Last applied", 4, context.getLastApplied()); + + sendMessageToSupport(new ApplyJournalEntries(5)); + + assertEquals("Last index", 5, context.getReplicatedLog().lastIndex()); + assertEquals("Last applied", 5, context.getLastApplied()); + assertEquals("Commit index", 5, context.getCommitIndex()); + assertEquals("Snapshot term", -1, context.getReplicatedLog().getSnapshotTerm()); + assertEquals("Snapshot index", -1, context.getReplicatedLog().getSnapshotIndex()); + + InOrder inOrder = Mockito.inOrder(mockCohort); + inOrder.verify(mockCohort).startLogRecoveryBatch(5); + + for(int i = 0; i < replicatedLog.size() - 1; i++) { + inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(i).getData()); + } + + inOrder.verify(mockCohort).applyCurrentLogRecoveryBatch(); + inOrder.verify(mockCohort).startLogRecoveryBatch(5); + inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(replicatedLog.size() - 1).getData()); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testOnApplyLogEntries() { + ReplicatedLog replicatedLog = context.getReplicatedLog(); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 0, new MockRaftActorContext.MockPayload("0"))); + + sendMessageToSupport(new ApplyLogEntries(0)); + + assertEquals("Last applied", 0, context.getLastApplied()); + assertEquals("Commit index", 0, context.getCommitIndex()); + } + + @Test + public void testOnSnapshotOffer() { + + ReplicatedLog replicatedLog = context.getReplicatedLog(); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 1, new MockRaftActorContext.MockPayload("1"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 2, new MockRaftActorContext.MockPayload("2"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 3, new MockRaftActorContext.MockPayload("3"))); + + byte[] snapshotBytes = {1,2,3,4,5}; + + ReplicatedLogEntry unAppliedEntry1 = new MockRaftActorContext.MockReplicatedLogEntry(1, + 4, new MockRaftActorContext.MockPayload("4", 4)); + + ReplicatedLogEntry unAppliedEntry2 = new MockRaftActorContext.MockReplicatedLogEntry(1, + 5, new MockRaftActorContext.MockPayload("5", 5)); + + int lastAppliedDuringSnapshotCapture = 3; + int lastIndexDuringSnapshotCapture = 5; + + Snapshot snapshot = Snapshot.create(snapshotBytes, Arrays.asList(unAppliedEntry1, unAppliedEntry2), + lastIndexDuringSnapshotCapture, 1, lastAppliedDuringSnapshotCapture, 1); + + SnapshotMetadata metadata = new SnapshotMetadata("test", 6, 12345); + SnapshotOffer snapshotOffer = new SnapshotOffer(metadata , snapshot); + + sendMessageToSupport(snapshotOffer); + + assertEquals("Journal log size", 2, context.getReplicatedLog().size()); + assertEquals("Journal data size", 9, context.getReplicatedLog().dataSize()); + assertEquals("Last index", lastIndexDuringSnapshotCapture, context.getReplicatedLog().lastIndex()); + assertEquals("Last applied", lastAppliedDuringSnapshotCapture, context.getLastApplied()); + assertEquals("Commit index", lastAppliedDuringSnapshotCapture, context.getCommitIndex()); + assertEquals("Snapshot term", 1, context.getReplicatedLog().getSnapshotTerm()); + assertEquals("Snapshot index", lastAppliedDuringSnapshotCapture, context.getReplicatedLog().getSnapshotIndex()); + + verify(mockCohort).applyRecoverySnapshot(snapshotBytes); + } + + @Test + public void testOnRecoveryCompletedWithRemainingBatch() { + ReplicatedLog replicatedLog = context.getReplicatedLog(); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 0, new MockRaftActorContext.MockPayload("0"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 1, new MockRaftActorContext.MockPayload("1"))); + + sendMessageToSupport(new ApplyJournalEntries(1)); + + sendMessageToSupport(RecoveryCompleted.getInstance(), true); + + assertEquals("Last applied", 1, context.getLastApplied()); + assertEquals("Commit index", 1, context.getCommitIndex()); + + InOrder inOrder = Mockito.inOrder(mockCohort); + inOrder.verify(mockCohort).startLogRecoveryBatch(anyInt()); + + for(int i = 0; i < replicatedLog.size(); i++) { + inOrder.verify(mockCohort).appendRecoveredLogEntry(replicatedLog.get(i).getData()); + } + + inOrder.verify(mockCohort).applyCurrentLogRecoveryBatch(); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testOnRecoveryCompletedWithNoRemainingBatch() { + sendMessageToSupport(RecoveryCompleted.getInstance(), true); + + verifyNoMoreInteractions(mockCohort); + } + + @Test + public void testOnDeprecatedDeleteEntries() { + ReplicatedLog replicatedLog = context.getReplicatedLog(); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 0, new MockRaftActorContext.MockPayload("0"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 1, new MockRaftActorContext.MockPayload("1"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 2, new MockRaftActorContext.MockPayload("2"))); + + sendMessageToSupport(new org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries(1)); + + assertEquals("Journal log size", 1, context.getReplicatedLog().size()); + assertEquals("Last index", 0, context.getReplicatedLog().lastIndex()); + } + + @Test + public void testOnDeleteEntries() { + ReplicatedLog replicatedLog = context.getReplicatedLog(); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 0, new MockRaftActorContext.MockPayload("0"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 1, new MockRaftActorContext.MockPayload("1"))); + replicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, + 2, new MockRaftActorContext.MockPayload("2"))); + + sendMessageToSupport(new DeleteEntries(1)); + + assertEquals("Journal log size", 1, context.getReplicatedLog().size()); + assertEquals("Last index", 0, context.getReplicatedLog().lastIndex()); + } + + @Test + public void testUpdateElectionTerm() { + + sendMessageToSupport(new UpdateElectionTerm(5, "member2")); + + assertEquals("Current term", 5, context.getTermInformation().getCurrentTerm()); + assertEquals("Voted For", "member2", context.getTermInformation().getVotedFor()); + } + + @Test + public void testRecoveryWithPersistenceDisabled() { + doReturn(false).when(mockPersistence).isRecoveryApplicable(); + + Snapshot snapshot = Snapshot.create(new byte[]{1}, Collections.emptyList(), 3, 1, 3, 1); + SnapshotOffer snapshotOffer = new SnapshotOffer(new SnapshotMetadata("test", 6, 12345), snapshot); + + sendMessageToSupport(snapshotOffer); + + sendMessageToSupport(new MockRaftActorContext.MockReplicatedLogEntry(1, + 4, new MockRaftActorContext.MockPayload("4"))); + sendMessageToSupport(new MockRaftActorContext.MockReplicatedLogEntry(1, + 5, new MockRaftActorContext.MockPayload("5"))); + + sendMessageToSupport(new ApplyJournalEntries(4)); + + sendMessageToSupport(new DeleteEntries(5)); + + sendMessageToSupport(new org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries(5)); + + assertEquals("Journal log size", 0, context.getReplicatedLog().size()); + assertEquals("Last index", -1, context.getReplicatedLog().lastIndex()); + assertEquals("Last applied", -1, context.getLastApplied()); + assertEquals("Commit index", -1, context.getCommitIndex()); + assertEquals("Snapshot term", -1, context.getReplicatedLog().getSnapshotTerm()); + assertEquals("Snapshot index", -1, context.getReplicatedLog().getSnapshotIndex()); + + sendMessageToSupport(new UpdateElectionTerm(5, "member2")); + + assertEquals("Current term", 0, context.getTermInformation().getCurrentTerm()); + assertEquals("Voted For", null, context.getTermInformation().getVotedFor()); + + sendMessageToSupport(RecoveryCompleted.getInstance(), true); + + verifyNoMoreInteractions(mockCohort); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotMessageSupportTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotMessageSupportTest.java new file mode 100644 index 0000000000..ae9c784a55 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorSnapshotMessageSupportTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import akka.actor.ActorRef; +import akka.japi.Procedure; +import akka.persistence.SaveSnapshotFailure; +import akka.persistence.SaveSnapshotSuccess; +import akka.persistence.SnapshotMetadata; +import java.util.Arrays; +import java.util.Collections; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockReplicatedLogEntry; +import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; +import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; +import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; +import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Unit tests for RaftActorSnapshotMessageSupport. + * + * @author Thomas Pantelis + */ +public class RaftActorSnapshotMessageSupportTest { + + private static final Logger LOG = LoggerFactory.getLogger(RaftActorRecoverySupportTest.class); + + @Mock + private DataPersistenceProvider mockPersistence; + + @Mock + private RaftActorBehavior mockBehavior; + + @Mock + private RaftActorSnapshotCohort mockCohort; + + @Mock + private SnapshotManager mockSnapshotManager; + + @Mock + ActorRef mockRaftActorRef; + + private RaftActorSnapshotMessageSupport support; + + private RaftActorContext context; + private final DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + context = new RaftActorContextImpl(mockRaftActorRef, null, "test", + new ElectionTermImpl(mockPersistence, "test", LOG), + -1, -1, Collections.emptyMap(), configParams, mockPersistence, LOG) { + @Override + public SnapshotManager getSnapshotManager() { + return mockSnapshotManager; + } + }; + + support = new RaftActorSnapshotMessageSupport(mockPersistence, context, mockBehavior, mockCohort); + + doReturn(true).when(mockPersistence).isRecoveryApplicable(); + + context.setReplicatedLog(ReplicatedLogImpl.newInstance(context, mockPersistence, mockBehavior)); + } + + private void sendMessageToSupport(Object message) { + sendMessageToSupport(message, true); + } + + private void sendMessageToSupport(Object message, boolean expHandled) { + boolean handled = support.handleSnapshotMessage(message); + assertEquals("complete", expHandled, handled); + } + + @Test + public void testOnApplySnapshot() { + + ReplicatedLog replicatedLog = context.getReplicatedLog(); + replicatedLog.append(new MockReplicatedLogEntry(1, 1, new MockPayload("1"))); + + byte[] snapshotBytes = {1,2,3,4,5}; + + ReplicatedLogEntry unAppliedEntry = new MockReplicatedLogEntry(1, 2, new MockPayload("2")); + + long lastAppliedDuringSnapshotCapture = 1; + long lastIndexDuringSnapshotCapture = 2; + + Snapshot snapshot = Snapshot.create(snapshotBytes, Arrays.asList(unAppliedEntry), + lastIndexDuringSnapshotCapture, 1, lastAppliedDuringSnapshotCapture, 1); + + sendMessageToSupport(new ApplySnapshot(snapshot)); + + assertEquals("Journal log size", 1, context.getReplicatedLog().size()); + assertEquals("Last index", lastIndexDuringSnapshotCapture, context.getReplicatedLog().lastIndex()); + assertEquals("Last applied", lastAppliedDuringSnapshotCapture, context.getLastApplied()); + assertEquals("Commit index", -1, context.getCommitIndex()); + assertEquals("Snapshot term", 1, context.getReplicatedLog().getSnapshotTerm()); + assertEquals("Snapshot index", lastAppliedDuringSnapshotCapture, context.getReplicatedLog().getSnapshotIndex()); + + verify(mockCohort).applySnapshot(snapshotBytes); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void testOnCaptureSnapshot() throws Exception { + + sendMessageToSupport(new CaptureSnapshot(3, 1, 2, 1, 2, 1, null)); + + ArgumentCaptor procedure = ArgumentCaptor.forClass(Procedure.class); + verify(mockSnapshotManager).create(procedure.capture()); + + procedure.getValue().apply(null); + + verify(mockCohort).createSnapshot(same(mockRaftActorRef)); + } + + @Test + public void testOnCaptureSnapshotReply() { + + byte[] snapshot = {1,2,3,4,5}; + sendMessageToSupport(new CaptureSnapshotReply(snapshot)); + + verify(mockSnapshotManager).persist(same(mockPersistence), same(snapshot), same(mockBehavior), anyLong()); + } + + @Test + public void testOnSaveSnapshotSuccess() { + + long sequenceNumber = 100; + sendMessageToSupport(new SaveSnapshotSuccess(new SnapshotMetadata("foo", sequenceNumber, 1234L))); + + verify(mockSnapshotManager).commit(mockPersistence, sequenceNumber); + } + + @Test + public void testOnSaveSnapshotFailure() { + + sendMessageToSupport(new SaveSnapshotFailure(new SnapshotMetadata("foo", 100, 1234L), + new Throwable("mock"))); + + verify(mockSnapshotManager).rollback(); + } + + @Test + public void testOnCommitSnapshot() { + + sendMessageToSupport(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT); + + verify(mockSnapshotManager).commit(mockPersistence, -1); + } + + @Test + public void testUnhandledMessage() { + + sendMessageToSupport("unhandled", false); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java index 17a81ac3c3..82ebcd1fbd 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java @@ -2,43 +2,32 @@ package org.opendaylight.controller.cluster.raft; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import akka.actor.ActorRef; -import akka.actor.ActorSystem; import akka.actor.PoisonPill; import akka.actor.Props; import akka.actor.Terminated; -import akka.japi.Creator; import akka.japi.Procedure; -import akka.pattern.Patterns; -import akka.persistence.RecoveryCompleted; import akka.persistence.SaveSnapshotFailure; import akka.persistence.SaveSnapshotSuccess; import akka.persistence.SnapshotMetadata; import akka.persistence.SnapshotOffer; -import akka.persistence.SnapshotSelectionCriteria; import akka.testkit.JavaTestKit; import akka.testkit.TestActorRef; -import akka.util.Timeout; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.Uninterruptibles; import com.google.protobuf.ByteString; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; @@ -46,16 +35,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.cluster.DataPersistenceProvider; import org.opendaylight.controller.cluster.NonPersistentDataProvider; -import org.opendaylight.controller.cluster.datastore.DataPersistenceProviderMonitor; import org.opendaylight.controller.cluster.notifications.LeaderStateChanged; import org.opendaylight.controller.cluster.notifications.RoleChanged; import org.opendaylight.controller.cluster.raft.RaftActor.UpdateElectionTerm; @@ -63,26 +48,26 @@ import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntrie import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; +import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; +import org.opendaylight.controller.cluster.raft.base.messages.DeleteEntries; import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat; import org.opendaylight.controller.cluster.raft.behaviors.Follower; import org.opendaylight.controller.cluster.raft.behaviors.Leader; import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; -import org.opendaylight.controller.cluster.raft.client.messages.FindLeader; -import org.opendaylight.controller.cluster.raft.client.messages.FindLeaderReply; import org.opendaylight.controller.cluster.raft.messages.AppendEntries; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; -import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; import org.opendaylight.controller.cluster.raft.utils.InMemoryJournal; import org.opendaylight.controller.cluster.raft.utils.InMemorySnapshotStore; import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; -import scala.concurrent.Await; -import scala.concurrent.Future; -import scala.concurrent.duration.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import scala.concurrent.duration.FiniteDuration; public class RaftActorTest extends AbstractActorTest { + static final Logger TEST_LOG = LoggerFactory.getLogger(RaftActorTest.class); + private TestActorFactory factory; @Before @@ -97,258 +82,6 @@ public class RaftActorTest extends AbstractActorTest { InMemorySnapshotStore.clear(); } - public static class MockRaftActor extends RaftActor { - - private final RaftActor delegate; - private final CountDownLatch recoveryComplete = new CountDownLatch(1); - private final List state; - private ActorRef roleChangeNotifier; - private final CountDownLatch initializeBehaviorComplete = new CountDownLatch(1); - - public static final class MockRaftActorCreator implements Creator { - private static final long serialVersionUID = 1L; - private final Map peerAddresses; - private final String id; - private final Optional config; - private final DataPersistenceProvider dataPersistenceProvider; - private final ActorRef roleChangeNotifier; - - private MockRaftActorCreator(Map peerAddresses, String id, - Optional config, DataPersistenceProvider dataPersistenceProvider, - ActorRef roleChangeNotifier) { - this.peerAddresses = peerAddresses; - this.id = id; - this.config = config; - this.dataPersistenceProvider = dataPersistenceProvider; - this.roleChangeNotifier = roleChangeNotifier; - } - - @Override - public MockRaftActor create() throws Exception { - MockRaftActor mockRaftActor = new MockRaftActor(id, peerAddresses, config, - dataPersistenceProvider); - mockRaftActor.roleChangeNotifier = this.roleChangeNotifier; - return mockRaftActor; - } - } - - public MockRaftActor(String id, Map peerAddresses, Optional config, - DataPersistenceProvider dataPersistenceProvider) { - super(id, peerAddresses, config); - state = new ArrayList<>(); - this.delegate = mock(RaftActor.class); - if(dataPersistenceProvider == null){ - setPersistence(true); - } else { - setPersistence(dataPersistenceProvider); - } - } - - public void waitForRecoveryComplete() { - try { - assertEquals("Recovery complete", true, recoveryComplete.await(5, TimeUnit.SECONDS)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - public void waitForInitializeBehaviorComplete() { - try { - assertEquals("Behavior initialized", true, initializeBehaviorComplete.await(5, TimeUnit.SECONDS)); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - - public void waitUntilLeader(){ - for(int i = 0;i < 10; i++){ - if(isLeader()){ - break; - } - Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); - } - } - - public List getState() { - return state; - } - - public static Props props(final String id, final Map peerAddresses, - Optional config){ - return Props.create(new MockRaftActorCreator(peerAddresses, id, config, null, null)); - } - - public static Props props(final String id, final Map peerAddresses, - Optional config, DataPersistenceProvider dataPersistenceProvider){ - return Props.create(new MockRaftActorCreator(peerAddresses, id, config, dataPersistenceProvider, null)); - } - - public static Props props(final String id, final Map peerAddresses, - Optional config, ActorRef roleChangeNotifier){ - return Props.create(new MockRaftActorCreator(peerAddresses, id, config, null, roleChangeNotifier)); - } - - public static Props props(final String id, final Map peerAddresses, - Optional config, ActorRef roleChangeNotifier, - DataPersistenceProvider dataPersistenceProvider){ - return Props.create(new MockRaftActorCreator(peerAddresses, id, config, dataPersistenceProvider, roleChangeNotifier)); - } - - - @Override protected void applyState(ActorRef clientActor, String identifier, Object data) { - delegate.applyState(clientActor, identifier, data); - LOG.info("{}: applyState called", persistenceId()); - } - - @Override - protected void startLogRecoveryBatch(int maxBatchSize) { - } - - @Override - protected void appendRecoveredLogEntry(Payload data) { - state.add(data); - } - - @Override - protected void applyCurrentLogRecoveryBatch() { - } - - @Override - protected void onRecoveryComplete() { - delegate.onRecoveryComplete(); - recoveryComplete.countDown(); - } - - @Override - protected void initializeBehavior() { - super.initializeBehavior(); - initializeBehaviorComplete.countDown(); - } - - @Override - protected void applyRecoverySnapshot(byte[] bytes) { - delegate.applyRecoverySnapshot(bytes); - try { - Object data = toObject(bytes); - if (data instanceof List) { - state.addAll((List) data); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - @Override protected void createSnapshot() { - LOG.info("{}: createSnapshot called", persistenceId()); - delegate.createSnapshot(); - } - - @Override protected void applySnapshot(byte [] snapshot) { - LOG.info("{}: applySnapshot called", persistenceId()); - delegate.applySnapshot(snapshot); - } - - @Override protected void onStateChanged() { - delegate.onStateChanged(); - } - - @Override - protected Optional getRoleChangeNotifier() { - return Optional.fromNullable(roleChangeNotifier); - } - - @Override public String persistenceId() { - return this.getId(); - } - - private Object toObject(byte[] bs) throws ClassNotFoundException, IOException { - Object obj = null; - ByteArrayInputStream bis = null; - ObjectInputStream ois = null; - try { - bis = new ByteArrayInputStream(bs); - ois = new ObjectInputStream(bis); - obj = ois.readObject(); - } finally { - if (bis != null) { - bis.close(); - } - if (ois != null) { - ois.close(); - } - } - return obj; - } - - public ReplicatedLog getReplicatedLog(){ - return this.getRaftActorContext().getReplicatedLog(); - } - - } - - - public static class RaftActorTestKit extends JavaTestKit { - private final ActorRef raftActor; - - public RaftActorTestKit(ActorSystem actorSystem, String actorName) { - super(actorSystem); - - raftActor = this.getSystem().actorOf(MockRaftActor.props(actorName, - Collections.emptyMap(), Optional.absent()), actorName); - - } - - - public ActorRef getRaftActor() { - return raftActor; - } - - public boolean waitForLogMessage(final Class logEventClass, String message){ - // Wait for a specific log message to show up - return - new JavaTestKit.EventFilter(logEventClass - ) { - @Override - protected Boolean run() { - return true; - } - }.from(raftActor.path().toString()) - .message(message) - .occurrences(1).exec(); - - - } - - protected void waitUntilLeader(){ - waitUntilLeader(raftActor); - } - - public static void waitUntilLeader(ActorRef actorRef) { - FiniteDuration duration = Duration.create(100, TimeUnit.MILLISECONDS); - for(int i = 0; i < 20 * 5; i++) { - Future future = Patterns.ask(actorRef, new FindLeader(), new Timeout(duration)); - try { - FindLeaderReply resp = (FindLeaderReply) Await.result(future, duration); - if(resp.getLeaderActor() != null) { - return; - } - } catch(TimeoutException e) { - } catch(Exception e) { - System.err.println("FindLeader threw ex"); - e.printStackTrace(); - } - - - Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS); - } - - Assert.fail("Leader not found for actorRef " + actorRef.path()); - } - - } - - @Test public void testConstruction() { new RaftActorTestKit(getSystem(), "testConstruction").waitUntilLeader(); @@ -361,18 +94,22 @@ public class RaftActorTest extends AbstractActorTest { } @Test - public void testRaftActorRecovery() throws Exception { + public void testRaftActorRecoveryWithPersistenceEnabled() throws Exception { + TEST_LOG.info("testRaftActorRecoveryWithPersistenceEnabled starting"); + new JavaTestKit(getSystem()) {{ String persistenceId = factory.generateActorId("follower-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); + // Set the heartbeat interval high to essentially disable election otherwise the test // may fail if the actor is switched to Leader and the commitIndex is set to the last // log entry. config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); + ImmutableMap peerAddresses = ImmutableMap.builder().put("member1", "address").build(); ActorRef followerActor = factory.createActor(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config)), persistenceId); + peerAddresses, Optional.of(config)), persistenceId); watch(followerActor); @@ -399,11 +136,11 @@ public class RaftActorTest extends AbstractActorTest { // add more entries after snapshot is taken List entries = new ArrayList<>(); ReplicatedLogEntry entry2 = new MockRaftActorContext.MockReplicatedLogEntry(1, 5, - new MockRaftActorContext.MockPayload("F")); + new MockRaftActorContext.MockPayload("F", 2)); ReplicatedLogEntry entry3 = new MockRaftActorContext.MockReplicatedLogEntry(1, 6, - new MockRaftActorContext.MockPayload("G")); + new MockRaftActorContext.MockPayload("G", 3)); ReplicatedLogEntry entry4 = new MockRaftActorContext.MockReplicatedLogEntry(1, 7, - new MockRaftActorContext.MockPayload("H")); + new MockRaftActorContext.MockPayload("H", 4)); entries.add(entry2); entries.add(entry3); entries.add(entry4); @@ -425,278 +162,151 @@ public class RaftActorTest extends AbstractActorTest { //reinstate the actor TestActorRef ref = factory.createTestActor( - MockRaftActor.props(persistenceId, Collections.emptyMap(), - Optional.of(config))); + MockRaftActor.props(persistenceId, peerAddresses, Optional.of(config))); + + MockRaftActor mockRaftActor = ref.underlyingActor(); - ref.underlyingActor().waitForRecoveryComplete(); + mockRaftActor.waitForRecoveryComplete(); - RaftActorContext context = ref.underlyingActor().getRaftActorContext(); + RaftActorContext context = mockRaftActor.getRaftActorContext(); assertEquals("Journal log size", snapshotUnappliedEntries.size() + entries.size(), context.getReplicatedLog().size()); + assertEquals("Journal data size", 10, context.getReplicatedLog().dataSize()); assertEquals("Last index", lastIndex, context.getReplicatedLog().lastIndex()); assertEquals("Last applied", lastAppliedToState, context.getLastApplied()); assertEquals("Commit index", lastAppliedToState, context.getCommitIndex()); - assertEquals("Recovered state size", 6, ref.underlyingActor().getState().size()); + assertEquals("Recovered state size", 6, mockRaftActor.getState().size()); + + mockRaftActor.waitForInitializeBehaviorComplete(); + + assertEquals("getRaftState", RaftState.Follower, mockRaftActor.getRaftState()); }}; + + TEST_LOG.info("testRaftActorRecoveryWithPersistenceEnabled ending"); } @Test - public void testRaftActorRecoveryWithPreLithuimApplyLogEntries() throws Exception { + public void testRaftActorRecoveryWithPersistenceDisabled() throws Exception { new JavaTestKit(getSystem()) {{ - String persistenceId = factory.generateActorId("leader-"); + String persistenceId = factory.generateActorId("follower-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - // Setup the persisted journal with some entries - ReplicatedLogEntry entry0 = new MockRaftActorContext.MockReplicatedLogEntry(1, 0, - new MockRaftActorContext.MockPayload("zero")); - ReplicatedLogEntry entry1 = new MockRaftActorContext.MockReplicatedLogEntry(1, 1, - new MockRaftActorContext.MockPayload("oen")); - ReplicatedLogEntry entry2 = new MockRaftActorContext.MockReplicatedLogEntry(1, 2, - new MockRaftActorContext.MockPayload("two")); + config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - long seqNr = 1; - InMemoryJournal.addEntry(persistenceId, seqNr++, entry0); - InMemoryJournal.addEntry(persistenceId, seqNr++, entry1); - InMemoryJournal.addEntry(persistenceId, seqNr++, new ApplyLogEntries(1)); - InMemoryJournal.addEntry(persistenceId, seqNr++, entry2); + TestActorRef ref = factory.createTestActor(MockRaftActor.props(persistenceId, + ImmutableMap.builder().put("member1", "address").build(), + Optional.of(config), new NonPersistentDataProvider()), persistenceId); - int lastAppliedToState = 1; - int lastIndex = 2; + MockRaftActor mockRaftActor = ref.underlyingActor(); - //reinstate the actor - TestActorRef leaderActor = factory.createTestActor( - MockRaftActor.props(persistenceId, Collections.emptyMap(), - Optional.of(config))); + mockRaftActor.waitForRecoveryComplete(); - leaderActor.underlyingActor().waitForRecoveryComplete(); + mockRaftActor.waitForInitializeBehaviorComplete(); - RaftActorContext context = leaderActor.underlyingActor().getRaftActorContext(); - assertEquals("Journal log size", 3, context.getReplicatedLog().size()); - assertEquals("Last index", lastIndex, context.getReplicatedLog().lastIndex()); - assertEquals("Last applied", lastAppliedToState, context.getLastApplied()); - assertEquals("Commit index", lastAppliedToState, context.getCommitIndex()); + assertEquals("getRaftState", RaftState.Follower, mockRaftActor.getRaftState()); }}; } - /** - * This test verifies that when recovery is applicable (typically when persistence is true) the RaftActor does - * process recovery messages - * - * @throws Exception - */ - @Test - public void testHandleRecoveryWhenDataPersistenceRecoveryApplicable() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); + public void testRaftActorForwardsToRaftActorRecoverySupport() { + String persistenceId = factory.generateActorId("leader-"); - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); + DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); + config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config)), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config)), persistenceId); - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); + MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - // Wait for akka's recovery to complete so it doesn't interfere. - mockRaftActor.waitForRecoveryComplete(); - - ByteString snapshotBytes = fromObject(Arrays.asList( - new MockRaftActorContext.MockPayload("A"), - new MockRaftActorContext.MockPayload("B"), - new MockRaftActorContext.MockPayload("C"), - new MockRaftActorContext.MockPayload("D"))); + // Wait for akka's recovery to complete so it doesn't interfere. + mockRaftActor.waitForRecoveryComplete(); - Snapshot snapshot = Snapshot.create(snapshotBytes.toByteArray(), - Lists.newArrayList(), 3, 1, 3, 1); + RaftActorRecoverySupport mockSupport = mock(RaftActorRecoverySupport.class); + mockRaftActor.setRaftActorRecoverySupport(mockSupport ); - mockRaftActor.onReceiveRecover(new SnapshotOffer(new SnapshotMetadata(persistenceId, 100, 100), snapshot)); + Snapshot snapshot = Snapshot.create(new byte[]{1}, Collections.emptyList(), 3, 1, 3, 1); + SnapshotOffer snapshotOffer = new SnapshotOffer(new SnapshotMetadata("test", 6, 12345), snapshot); + mockRaftActor.handleRecover(snapshotOffer); - verify(mockRaftActor.delegate).applyRecoverySnapshot(eq(snapshotBytes.toByteArray())); + MockRaftActorContext.MockReplicatedLogEntry logEntry = new MockRaftActorContext.MockReplicatedLogEntry(1, + 1, new MockRaftActorContext.MockPayload("1", 5)); + mockRaftActor.handleRecover(logEntry); - mockRaftActor.onReceiveRecover(new ReplicatedLogImplEntry(0, 1, new MockRaftActorContext.MockPayload("A"))); + ApplyJournalEntries applyJournalEntries = new ApplyJournalEntries(2); + mockRaftActor.handleRecover(applyJournalEntries); - ReplicatedLog replicatedLog = mockRaftActor.getReplicatedLog(); + ApplyLogEntries applyLogEntries = new ApplyLogEntries(0); + mockRaftActor.handleRecover(applyLogEntries); - assertEquals("add replicated log entry", 1, replicatedLog.size()); + DeleteEntries deleteEntries = new DeleteEntries(1); + mockRaftActor.handleRecover(deleteEntries); - mockRaftActor.onReceiveRecover(new ReplicatedLogImplEntry(1, 1, new MockRaftActorContext.MockPayload("A"))); + org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries deprecatedDeleteEntries = + new org.opendaylight.controller.cluster.raft.RaftActor.DeleteEntries(1); + mockRaftActor.handleRecover(deprecatedDeleteEntries); - assertEquals("add replicated log entry", 2, replicatedLog.size()); + UpdateElectionTerm updateElectionTerm = new UpdateElectionTerm(5, "member2"); + mockRaftActor.handleRecover(updateElectionTerm); - mockRaftActor.onReceiveRecover(new ApplyJournalEntries(1)); - - assertEquals("commit index 1", 1, mockRaftActor.getRaftActorContext().getCommitIndex()); - - // The snapshot had 4 items + we added 2 more items during the test - // We start removing from 5 and we should get 1 item in the replicated log - mockRaftActor.onReceiveRecover(new RaftActor.DeleteEntries(5)); - - assertEquals("remove log entries", 1, replicatedLog.size()); - - mockRaftActor.onReceiveRecover(new UpdateElectionTerm(10, "foobar")); - - assertEquals("election term", 10, mockRaftActor.getRaftActorContext().getTermInformation().getCurrentTerm()); - assertEquals("voted for", "foobar", mockRaftActor.getRaftActorContext().getTermInformation().getVotedFor()); - - mockRaftActor.onReceiveRecover(mock(RecoveryCompleted.class)); - - }}; + verify(mockSupport).handleRecoveryMessage(same(snapshotOffer)); + verify(mockSupport).handleRecoveryMessage(same(logEntry)); + verify(mockSupport).handleRecoveryMessage(same(applyJournalEntries)); + verify(mockSupport).handleRecoveryMessage(same(applyLogEntries)); + verify(mockSupport).handleRecoveryMessage(same(deleteEntries)); + verify(mockSupport).handleRecoveryMessage(same(deprecatedDeleteEntries)); + verify(mockSupport).handleRecoveryMessage(same(updateElectionTerm)); } - /** - * This test verifies that when recovery is not applicable (typically when persistence is false) the RaftActor does - * not process recovery messages - * - * @throws Exception - */ @Test - public void testHandleRecoveryWhenDataPersistenceRecoveryNotApplicable() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); + public void testRaftActorForwardsToRaftActorSnapshotMessageSupport() { + String persistenceId = factory.generateActorId("leader-"); - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - - TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), new DataPersistenceProviderMonitor()), persistenceId); - - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - - // Wait for akka's recovery to complete so it doesn't interfere. - mockRaftActor.waitForRecoveryComplete(); - - ByteString snapshotBytes = fromObject(Arrays.asList( - new MockRaftActorContext.MockPayload("A"), - new MockRaftActorContext.MockPayload("B"), - new MockRaftActorContext.MockPayload("C"), - new MockRaftActorContext.MockPayload("D"))); - - Snapshot snapshot = Snapshot.create(snapshotBytes.toByteArray(), - Lists.newArrayList(), 3, 1, 3, 1); - - mockRaftActor.onReceiveRecover(new SnapshotOffer(new SnapshotMetadata(persistenceId, 100, 100), snapshot)); - - verify(mockRaftActor.delegate, times(0)).applyRecoverySnapshot(any(byte[].class)); - - mockRaftActor.onReceiveRecover(new ReplicatedLogImplEntry(0, 1, new MockRaftActorContext.MockPayload("A"))); - - ReplicatedLog replicatedLog = mockRaftActor.getReplicatedLog(); - - assertEquals("add replicated log entry", 0, replicatedLog.size()); - - mockRaftActor.onReceiveRecover(new ReplicatedLogImplEntry(1, 1, new MockRaftActorContext.MockPayload("A"))); - - assertEquals("add replicated log entry", 0, replicatedLog.size()); - - mockRaftActor.onReceiveRecover(new ApplyJournalEntries(1)); - - assertEquals("commit index -1", -1, mockRaftActor.getRaftActorContext().getCommitIndex()); - - mockRaftActor.onReceiveRecover(new RaftActor.DeleteEntries(2)); + DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - assertEquals("remove log entries", 0, replicatedLog.size()); + config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - mockRaftActor.onReceiveRecover(new UpdateElectionTerm(10, "foobar")); + RaftActorSnapshotMessageSupport mockSupport = mock(RaftActorSnapshotMessageSupport.class); - assertNotEquals("election term", 10, mockRaftActor.getRaftActorContext().getTermInformation().getCurrentTerm()); - assertNotEquals("voted for", "foobar", mockRaftActor.getRaftActorContext().getTermInformation().getVotedFor()); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), mockSupport), persistenceId); - mockRaftActor.onReceiveRecover(mock(RecoveryCompleted.class)); - }}; - } + MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); + // Wait for akka's recovery to complete so it doesn't interfere. + mockRaftActor.waitForRecoveryComplete(); - @Test - public void testUpdatingElectionTermCallsDataPersistence() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); + ApplySnapshot applySnapshot = new ApplySnapshot(mock(Snapshot.class)); + doReturn(true).when(mockSupport).handleSnapshotMessage(same(applySnapshot)); + mockRaftActor.handleCommand(applySnapshot); - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); + CaptureSnapshot captureSnapshot = new CaptureSnapshot(1, 1, 1, 1, 0, 1, null); + doReturn(true).when(mockSupport).handleSnapshotMessage(same(captureSnapshot)); + mockRaftActor.handleCommand(captureSnapshot); - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); + CaptureSnapshotReply captureSnapshotReply = new CaptureSnapshotReply(new byte[0]); + doReturn(true).when(mockSupport).handleSnapshotMessage(same(captureSnapshotReply)); + mockRaftActor.handleCommand(captureSnapshotReply); - CountDownLatch persistLatch = new CountDownLatch(1); - DataPersistenceProviderMonitor dataPersistenceProviderMonitor = new DataPersistenceProviderMonitor(); - dataPersistenceProviderMonitor.setPersistLatch(persistLatch); + SaveSnapshotSuccess saveSnapshotSuccess = new SaveSnapshotSuccess(mock(SnapshotMetadata.class)); + doReturn(true).when(mockSupport).handleSnapshotMessage(same(saveSnapshotSuccess)); + mockRaftActor.handleCommand(saveSnapshotSuccess); - TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); + SaveSnapshotFailure saveSnapshotFailure = new SaveSnapshotFailure(mock(SnapshotMetadata.class), new Throwable()); + doReturn(true).when(mockSupport).handleSnapshotMessage(same(saveSnapshotFailure)); + mockRaftActor.handleCommand(saveSnapshotFailure); - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - - mockRaftActor.waitForInitializeBehaviorComplete(); + doReturn(true).when(mockSupport).handleSnapshotMessage(same(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT)); + mockRaftActor.handleCommand(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT); - mockRaftActor.getRaftActorContext().getTermInformation().updateAndPersist(10, "foobar"); - - assertEquals("Persist called", true, persistLatch.await(5, TimeUnit.SECONDS)); - } - }; - } - - @Test - public void testAddingReplicatedLogEntryCallsDataPersistence() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); - - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - - DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - - TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); - - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - - mockRaftActor.waitForInitializeBehaviorComplete(); - - MockRaftActorContext.MockReplicatedLogEntry logEntry = new MockRaftActorContext.MockReplicatedLogEntry(10, 10, mock(Payload.class)); - - mockRaftActor.getRaftActorContext().getReplicatedLog().appendAndPersist(logEntry); - - verify(dataPersistenceProvider).persist(eq(logEntry), any(Procedure.class)); - } - }; - } - - @Test - public void testRemovingReplicatedLogEntryCallsDataPersistence() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); - - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - - DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - - TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); - - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - - mockRaftActor.waitForInitializeBehaviorComplete(); - - mockRaftActor.waitUntilLeader(); - - mockRaftActor.getReplicatedLog().appendAndPersist(new MockRaftActorContext.MockReplicatedLogEntry(1, 0, mock(Payload.class))); - - mockRaftActor.getRaftActorContext().getReplicatedLog().removeFromAndPersist(0); - - verify(dataPersistenceProvider, times(3)).persist(anyObject(), any(Procedure.class)); - } - }; + verify(mockSupport).handleSnapshotMessage(same(applySnapshot)); + verify(mockSupport).handleSnapshotMessage(same(captureSnapshot)); + verify(mockSupport).handleSnapshotMessage(same(captureSnapshotReply)); + verify(mockSupport).handleSnapshotMessage(same(saveSnapshotSuccess)); + verify(mockSupport).handleSnapshotMessage(same(saveSnapshotFailure)); + verify(mockSupport).handleSnapshotMessage(same(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT)); } @Test @@ -729,112 +339,6 @@ public class RaftActorTest extends AbstractActorTest { }; } - @Test - public void testCaptureSnapshotReplyCallsDataPersistence() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); - - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - - DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - - TestActorRef mockActorRef = factory.createTestActor( - MockRaftActor.props(persistenceId, Collections.emptyMap(), - Optional.of(config), dataPersistenceProvider), persistenceId); - - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - - mockRaftActor.waitForInitializeBehaviorComplete(); - - ByteString snapshotBytes = fromObject(Arrays.asList( - new MockRaftActorContext.MockPayload("A"), - new MockRaftActorContext.MockPayload("B"), - new MockRaftActorContext.MockPayload("C"), - new MockRaftActorContext.MockPayload("D"))); - - RaftActorContext raftActorContext = mockRaftActor.getRaftActorContext(); - - raftActorContext.getSnapshotManager().capture( - new MockRaftActorContext.MockReplicatedLogEntry(1, -1, - new MockRaftActorContext.MockPayload("D")), -1); - - mockRaftActor.setCurrentBehavior(new Leader(raftActorContext)); - - mockRaftActor.onReceiveCommand(new CaptureSnapshotReply(snapshotBytes.toByteArray())); - - verify(dataPersistenceProvider).saveSnapshot(anyObject()); - - } - }; - } - - @Test - public void testSaveSnapshotSuccessCallsDataPersistence() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); - - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - - DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - - TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, - ImmutableMap.of("leader", "fake/path"), Optional.of(config), dataPersistenceProvider), persistenceId); - - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - - mockRaftActor.waitForInitializeBehaviorComplete(); - MockRaftActorContext.MockReplicatedLogEntry lastEntry = new MockRaftActorContext.MockReplicatedLogEntry(1, 4, mock(Payload.class)); - - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 0, mock(Payload.class))); - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 1, mock(Payload.class))); - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 2, mock(Payload.class))); - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 3, mock(Payload.class))); - mockRaftActor.getReplicatedLog().append(lastEntry); - - ByteString snapshotBytes = fromObject(Arrays.asList( - new MockRaftActorContext.MockPayload("A"), - new MockRaftActorContext.MockPayload("B"), - new MockRaftActorContext.MockPayload("C"), - new MockRaftActorContext.MockPayload("D"))); - - RaftActorContext raftActorContext = mockRaftActor.getRaftActorContext(); - mockRaftActor.setCurrentBehavior(new Follower(raftActorContext)); - - long replicatedToAllIndex = 1; - - mockRaftActor.getRaftActorContext().getSnapshotManager().capture(lastEntry, replicatedToAllIndex); - - verify(mockRaftActor.delegate).createSnapshot(); - - mockRaftActor.onReceiveCommand(new CaptureSnapshotReply(snapshotBytes.toByteArray())); - - mockRaftActor.onReceiveCommand(new SaveSnapshotSuccess(new SnapshotMetadata("foo", 100, 100))); - - verify(dataPersistenceProvider).deleteSnapshots(any(SnapshotSelectionCriteria.class)); - - verify(dataPersistenceProvider).deleteMessages(100); - - assertEquals(3, mockRaftActor.getReplicatedLog().size()); - assertEquals(1, mockRaftActor.getCurrentBehavior().getReplicatedToAllIndex()); - - assertNotNull(mockRaftActor.getReplicatedLog().get(2)); - assertNotNull(mockRaftActor.getReplicatedLog().get(3)); - assertNotNull(mockRaftActor.getReplicatedLog().get(4)); - - // Index 2 will not be in the log because it was removed due to snapshotting - assertNull(mockRaftActor.getReplicatedLog().get(1)); - assertNull(mockRaftActor.getReplicatedLog().get(0)); - - } - }; - } - @Test public void testApplyState() throws Exception { @@ -860,107 +364,7 @@ public class RaftActorTest extends AbstractActorTest { mockRaftActor.onReceiveCommand(new ApplyState(mockActorRef, "apply-state", entry)); - verify(mockRaftActor.delegate).applyState(eq(mockActorRef), eq("apply-state"), anyObject()); - - } - }; - } - - @Test - public void testApplySnapshot() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); - - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - - DataPersistenceProviderMonitor dataPersistenceProviderMonitor = new DataPersistenceProviderMonitor(); - - TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); - - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - - mockRaftActor.waitForInitializeBehaviorComplete(); - - ReplicatedLog oldReplicatedLog = mockRaftActor.getReplicatedLog(); - - oldReplicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, 0, mock(Payload.class))); - oldReplicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, 1, mock(Payload.class))); - oldReplicatedLog.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 2, - mock(Payload.class))); - - ByteString snapshotBytes = fromObject(Arrays.asList( - new MockRaftActorContext.MockPayload("A"), - new MockRaftActorContext.MockPayload("B"), - new MockRaftActorContext.MockPayload("C"), - new MockRaftActorContext.MockPayload("D"))); - - Snapshot snapshot = mock(Snapshot.class); - - doReturn(snapshotBytes.toByteArray()).when(snapshot).getState(); - - doReturn(3L).when(snapshot).getLastAppliedIndex(); - - mockRaftActor.onReceiveCommand(new ApplySnapshot(snapshot)); - - verify(mockRaftActor.delegate).applySnapshot(eq(snapshot.getState())); - - assertTrue("The replicatedLog should have changed", - oldReplicatedLog != mockRaftActor.getReplicatedLog()); - - assertEquals("lastApplied should be same as in the snapshot", - (Long) 3L, mockRaftActor.getLastApplied()); - - assertEquals(0, mockRaftActor.getReplicatedLog().size()); - - } - }; - } - - @Test - public void testSaveSnapshotFailure() throws Exception { - new JavaTestKit(getSystem()) { - { - String persistenceId = factory.generateActorId("leader-"); - - DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - - config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - - DataPersistenceProviderMonitor dataPersistenceProviderMonitor = new DataPersistenceProviderMonitor(); - - TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); - - MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - - mockRaftActor.waitForInitializeBehaviorComplete(); - - ByteString snapshotBytes = fromObject(Arrays.asList( - new MockRaftActorContext.MockPayload("A"), - new MockRaftActorContext.MockPayload("B"), - new MockRaftActorContext.MockPayload("C"), - new MockRaftActorContext.MockPayload("D"))); - - RaftActorContext raftActorContext = mockRaftActor.getRaftActorContext(); - - mockRaftActor.setCurrentBehavior(new Leader(raftActorContext)); - - raftActorContext.getSnapshotManager().capture( - new MockRaftActorContext.MockReplicatedLogEntry(1, 1, - new MockRaftActorContext.MockPayload("D")), 1); - - mockRaftActor.onReceiveCommand(new CaptureSnapshotReply(snapshotBytes.toByteArray())); - - mockRaftActor.onReceiveCommand(new SaveSnapshotFailure(new SnapshotMetadata("foobar", 10L, 1234L), - new Exception())); - - assertEquals("Snapshot index should not have advanced because save snapshot failed", -1, - mockRaftActor.getReplicatedLog().getSnapshotIndex()); + verify(mockRaftActor.actorDelegate).applyState(eq(mockActorRef), eq("apply-state"), anyObject()); } }; @@ -1131,7 +535,7 @@ public class RaftActorTest extends AbstractActorTest { .capture(new MockRaftActorContext.MockReplicatedLogEntry(1, 6, new MockRaftActorContext.MockPayload("x")), 4); - verify(leaderActor.delegate).createSnapshot(); + verify(leaderActor.snapshotCohortDelegate).createSnapshot(any(ActorRef.class)); assertEquals(8, leaderActor.getReplicatedLog().size()); @@ -1230,7 +634,7 @@ public class RaftActorTest extends AbstractActorTest { new MockRaftActorContext.MockReplicatedLogEntry(1, 5, new MockRaftActorContext.MockPayload("D")), 4); - verify(followerActor.delegate).createSnapshot(); + verify(followerActor.snapshotCohortDelegate).createSnapshot(any(ActorRef.class)); assertEquals(6, followerActor.getReplicatedLog().size()); @@ -1466,7 +870,7 @@ public class RaftActorTest extends AbstractActorTest { }}; } - private ByteString fromObject(Object snapshot) throws Exception { + public static ByteString fromObject(Object snapshot) throws Exception { ByteArrayOutputStream b = null; ObjectOutputStream o = null; try { diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTestKit.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTestKit.java new file mode 100644 index 0000000000..3e747e387e --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTestKit.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.pattern.Patterns; +import akka.testkit.JavaTestKit; +import akka.util.Timeout; +import com.google.common.base.Optional; +import com.google.common.util.concurrent.Uninterruptibles; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.Assert; +import org.opendaylight.controller.cluster.raft.client.messages.FindLeader; +import org.opendaylight.controller.cluster.raft.client.messages.FindLeaderReply; +import scala.concurrent.Await; +import scala.concurrent.Future; +import scala.concurrent.duration.Duration; +import scala.concurrent.duration.FiniteDuration; + +public class RaftActorTestKit extends JavaTestKit { + private final ActorRef raftActor; + + public RaftActorTestKit(ActorSystem actorSystem, String actorName) { + super(actorSystem); + + raftActor = this.getSystem().actorOf(MockRaftActor.props(actorName, + Collections.emptyMap(), Optional.absent()), actorName); + + } + + + public ActorRef getRaftActor() { + return raftActor; + } + + public boolean waitForLogMessage(final Class logEventClass, String message){ + // Wait for a specific log message to show up + return + new JavaTestKit.EventFilter(logEventClass + ) { + @Override + protected Boolean run() { + return true; + } + }.from(raftActor.path().toString()) + .message(message) + .occurrences(1).exec(); + + + } + + protected void waitUntilLeader(){ + waitUntilLeader(raftActor); + } + + public static void waitUntilLeader(ActorRef actorRef) { + FiniteDuration duration = Duration.create(100, TimeUnit.MILLISECONDS); + for(int i = 0; i < 20 * 5; i++) { + Future future = Patterns.ask(actorRef, new FindLeader(), new Timeout(duration)); + try { + FindLeaderReply resp = (FindLeaderReply) Await.result(future, duration); + if(resp.getLeaderActor() != null) { + return; + } + } catch(TimeoutException e) { + } catch(Exception e) { + System.err.println("FindLeader threw ex"); + e.printStackTrace(); + } + + + Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS); + } + + Assert.fail("Leader not found for actorRef " + actorRef.path()); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RecoveryIntegrationTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RecoveryIntegrationTest.java new file mode 100644 index 0000000000..a8f490e751 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RecoveryIntegrationTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import static org.junit.Assert.assertEquals; +import akka.persistence.SaveSnapshotSuccess; +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload; +import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntries; +import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; +import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; +import org.opendaylight.controller.cluster.raft.messages.AppendEntries; +import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; + +/** + * Tests raft actor persistence recovery end-to-end using real RaftActors and behavior communication. + * + * @author Thomas Pantelis + */ +public class RecoveryIntegrationTest extends AbstractRaftActorIntegrationTest { + + private MockPayload payload0; + private MockPayload payload1; + + @Before + public void setup() { + follower1Actor = newTestRaftActor(follower1Id, ImmutableMap.of(leaderId, testActorPath(leaderId)), + newFollowerConfigParams()); + + peerAddresses = ImmutableMap.builder(). + put(follower1Id, follower1Actor.path().toString()).build(); + + leaderConfigParams = newLeaderConfigParams(); + leaderActor = newTestRaftActor(leaderId, peerAddresses, leaderConfigParams); + + follower1CollectorActor = follower1Actor.underlyingActor().collectorActor(); + leaderCollectorActor = leaderActor.underlyingActor().collectorActor(); + + leaderContext = leaderActor.underlyingActor().getRaftActorContext(); + } + + @Test + public void testStatePersistedBetweenSnapshotCaptureAndPersist() { + + send2InitialPayloads(); + + // Block these messages initially so we can control the sequence. + leaderActor.underlyingActor().startDropMessages(CaptureSnapshot.class); + leaderActor.underlyingActor().startDropMessages(CaptureSnapshotReply.class); + follower1Actor.underlyingActor().startDropMessages(AppendEntries.class); + + MockPayload payload2 = sendPayloadData(leaderActor, "two"); + + // This should trigger a snapshot. + MockPayload payload3 = sendPayloadData(leaderActor, "three"); + + MessageCollectorActor.expectMatching(follower1CollectorActor, AppendEntries.class, 3); + + CaptureSnapshot captureSnapshot = MessageCollectorActor.expectFirstMatching( + leaderCollectorActor, CaptureSnapshot.class); + + // First, deliver the CaptureSnapshot to the leader. + leaderActor.underlyingActor().stopDropMessages(CaptureSnapshot.class); + leaderActor.tell(captureSnapshot, leaderActor); + + // Send another payload. + MockPayload payload4 = sendPayloadData(leaderActor, "four"); + + // Now deliver the AppendEntries to the follower + follower1Actor.underlyingActor().stopDropMessages(AppendEntries.class); + + MessageCollectorActor.expectMatching(leaderCollectorActor, ApplyJournalEntries.class, 3); + + // Now deliver the CaptureSnapshotReply to the leader. + CaptureSnapshotReply captureSnapshotReply = MessageCollectorActor.expectFirstMatching( + leaderCollectorActor, CaptureSnapshotReply.class); + leaderActor.underlyingActor().stopDropMessages(CaptureSnapshotReply.class); + leaderActor.tell(captureSnapshotReply, leaderActor); + + // Wait for snapshot complete. + MessageCollectorActor.expectFirstMatching(leaderCollectorActor, SaveSnapshotSuccess.class); + + reinstateLeaderActor(); + + assertEquals("Leader snapshot term", currentTerm, leaderContext.getReplicatedLog().getSnapshotTerm()); + assertEquals("Leader snapshot index", 1, leaderContext.getReplicatedLog().getSnapshotIndex()); + assertEquals("Leader journal log size", 3, leaderContext.getReplicatedLog().size()); + assertEquals("Leader journal last index", 4, leaderContext.getReplicatedLog().lastIndex()); + assertEquals("Leader commit index", 4, leaderContext.getCommitIndex()); + assertEquals("Leader last applied", 4, leaderContext.getLastApplied()); + + assertEquals("Leader state", Arrays.asList(payload0, payload1, payload2, payload3, payload4), + leaderActor.underlyingActor().getState()); + } + + @Test + public void testStatePersistedBetweenInitiateSnapshotAndCapture() { + + send2InitialPayloads(); + + // Block these messages initially so we can control the sequence. + leaderActor.underlyingActor().startDropMessages(CaptureSnapshot.class); + follower1Actor.underlyingActor().startDropMessages(AppendEntries.class); + + MockPayload payload2 = sendPayloadData(leaderActor, "two"); + + // This should trigger a snapshot. + MockPayload payload3 = sendPayloadData(leaderActor, "three"); + + // Send another payload. + MockPayload payload4 = sendPayloadData(leaderActor, "four"); + + MessageCollectorActor.expectMatching(follower1CollectorActor, AppendEntries.class, 3); + + CaptureSnapshot captureSnapshot = MessageCollectorActor.expectFirstMatching( + leaderCollectorActor, CaptureSnapshot.class); + + // First, deliver the AppendEntries to the follower + follower1Actor.underlyingActor().stopDropMessages(AppendEntries.class); + + MessageCollectorActor.expectMatching(leaderCollectorActor, ApplyJournalEntries.class, 3); + + // Now deliver the CaptureSnapshot to the leader. + leaderActor.underlyingActor().stopDropMessages(CaptureSnapshot.class); + leaderActor.tell(captureSnapshot, leaderActor); + + // Wait for snapshot complete. + MessageCollectorActor.expectFirstMatching(leaderCollectorActor, SaveSnapshotSuccess.class); + + reinstateLeaderActor(); + + assertEquals("Leader snapshot term", currentTerm, leaderContext.getReplicatedLog().getSnapshotTerm()); + assertEquals("Leader snapshot index", 1, leaderContext.getReplicatedLog().getSnapshotIndex()); + assertEquals("Leader journal log size", 3, leaderContext.getReplicatedLog().size()); + assertEquals("Leader journal last index", 4, leaderContext.getReplicatedLog().lastIndex()); + assertEquals("Leader commit index", 4, leaderContext.getCommitIndex()); + assertEquals("Leader last applied", 4, leaderContext.getLastApplied()); + + // payloads 2, 3, and 4 were applied after the snapshot was initiated and before it was captured so + // were included in the snapshot. They were also included as unapplied entries in the snapshot as + // they weren't yet applied to the state at the time the snapshot was initiated. They were applied to the + // state on recovery by the ApplyJournalEntries messages which remained in the persisted log. + // This is a side effect of trimming the persisted log to the sequence number captured at the time + // the snapshot was initiated. + assertEquals("Leader state", Arrays.asList(payload0, payload1, payload2, payload3, payload4, payload2, + payload3, payload4), leaderActor.underlyingActor().getState()); + } + + @Test + public void testApplyJournalEntriesPersistedAfterSnapshotPersisted() { + + send2InitialPayloads(); + + // Block these messages initially so we can control the sequence. + follower1Actor.underlyingActor().startDropMessages(AppendEntries.class); + + MockPayload payload2 = sendPayloadData(leaderActor, "two"); + + // This should trigger a snapshot. + MockPayload payload3 = sendPayloadData(leaderActor, "three"); + + // Send another payload. + MockPayload payload4 = sendPayloadData(leaderActor, "four"); + + MessageCollectorActor.expectMatching(follower1CollectorActor, AppendEntries.class, 3); + + // Wait for snapshot complete. + MessageCollectorActor.expectFirstMatching(leaderCollectorActor, SaveSnapshotSuccess.class); + + // Now deliver the AppendEntries to the follower + follower1Actor.underlyingActor().stopDropMessages(AppendEntries.class); + + MessageCollectorActor.expectMatching(leaderCollectorActor, ApplyJournalEntries.class, 3); + + reinstateLeaderActor(); + + assertEquals("Leader snapshot term", currentTerm, leaderContext.getReplicatedLog().getSnapshotTerm()); + assertEquals("Leader snapshot index", 1, leaderContext.getReplicatedLog().getSnapshotIndex()); + assertEquals("Leader journal log size", 3, leaderContext.getReplicatedLog().size()); + assertEquals("Leader journal last index", 4, leaderContext.getReplicatedLog().lastIndex()); + assertEquals("Leader commit index", 4, leaderContext.getCommitIndex()); + assertEquals("Leader last applied", 4, leaderContext.getLastApplied()); + + assertEquals("Leader state", Arrays.asList(payload0, payload1, payload2, payload3, payload4), + leaderActor.underlyingActor().getState()); + } + + private void reinstateLeaderActor() { + killActor(leaderActor); + + leaderActor = newTestRaftActor(leaderId, peerAddresses, leaderConfigParams); + + leaderActor.underlyingActor().waitForRecoveryComplete(); + + leaderContext = leaderActor.underlyingActor().getRaftActorContext(); + } + + private void send2InitialPayloads() { + waitUntilLeader(leaderActor); + currentTerm = leaderContext.getTermInformation().getCurrentTerm(); + + payload0 = sendPayloadData(leaderActor, "zero"); + payload1 = sendPayloadData(leaderActor, "one"); + + // Verify the leader applies the states. + MessageCollectorActor.expectMatching(leaderCollectorActor, ApplyJournalEntries.class, 2); + + assertEquals("Leader last applied", 1, leaderContext.getLastApplied()); + + MessageCollectorActor.clearMessages(leaderCollectorActor); + MessageCollectorActor.clearMessages(follower1CollectorActor); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicatedLogImplTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicatedLogImplTest.java new file mode 100644 index 0000000000..92e384e19a --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicatedLogImplTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import akka.japi.Procedure; +import com.google.common.base.Supplier; +import java.util.Collections; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.internal.matchers.Same; +import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockReplicatedLogEntry; +import org.opendaylight.controller.cluster.raft.base.messages.DeleteEntries; +import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Unit tests for ReplicatedLogImpl. + * + * @author Thomas Pantelis + */ +public class ReplicatedLogImplTest { + private static final Logger LOG = LoggerFactory.getLogger(RaftActorRecoverySupportTest.class); + + @Mock + private DataPersistenceProvider mockPersistence; + + @Mock + private RaftActorBehavior mockBehavior; + + @Mock + private SnapshotManager mockSnapshotManager; + + private RaftActorContext context; + private final DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + context = new RaftActorContextImpl(null, null, "test", + new ElectionTermImpl(mockPersistence, "test", LOG), + -1, -1, Collections.emptyMap(), configParams, mockPersistence, LOG) { + @Override + public SnapshotManager getSnapshotManager() { + return mockSnapshotManager; + } + }; + } + + private void verifyPersist(Object message) throws Exception { + verifyPersist(message, new Same(message)); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void verifyPersist(Object message, Matcher matcher) throws Exception { + ArgumentCaptor procedure = ArgumentCaptor.forClass(Procedure.class); + verify(mockPersistence).persist(Matchers.argThat(matcher), procedure.capture()); + + procedure.getValue().apply(message); + } + + @SuppressWarnings("unchecked") + @Test + public void testAppendAndPersistExpectingNoCapture() throws Exception { + ReplicatedLog log = ReplicatedLogImpl.newInstance(context, mockPersistence, mockBehavior); + + MockReplicatedLogEntry logEntry = new MockReplicatedLogEntry(1, 1, new MockPayload("1")); + + log.appendAndPersist(logEntry); + + verifyPersist(logEntry); + + assertEquals("size", 1, log.size()); + + reset(mockPersistence); + + Procedure mockCallback = Mockito.mock(Procedure.class); + log.appendAndPersist(logEntry, mockCallback); + + verifyPersist(logEntry); + + verify(mockCallback).apply(same(logEntry)); + verifyNoMoreInteractions(mockSnapshotManager); + + assertEquals("size", 2, log.size()); + } + + @Test + public void testAppendAndPersistExpectingCaptureDueToJournalCount() throws Exception { + configParams.setSnapshotBatchCount(2); + + doReturn(1L).when(mockBehavior).getReplicatedToAllIndex(); + + ReplicatedLog log = ReplicatedLogImpl.newInstance(context, mockPersistence, mockBehavior); + + MockReplicatedLogEntry logEntry1 = new MockReplicatedLogEntry(1, 2, new MockPayload("2")); + MockReplicatedLogEntry logEntry2 = new MockReplicatedLogEntry(1, 3, new MockPayload("3")); + + log.appendAndPersist(logEntry1); + verifyPersist(logEntry1); + + verifyNoMoreInteractions(mockSnapshotManager); + reset(mockPersistence); + + log.appendAndPersist(logEntry2); + verifyPersist(logEntry2); + + verify(mockSnapshotManager).capture(same(logEntry2), eq(1L)); + + assertEquals("size", 2, log.size()); + } + + @Test + public void testAppendAndPersistExpectingCaptureDueToDataSize() throws Exception { + doReturn(1L).when(mockBehavior).getReplicatedToAllIndex(); + + context.setTotalMemoryRetriever(new Supplier() { + @Override + public Long get() { + return 100L; + } + }); + + ReplicatedLog log = ReplicatedLogImpl.newInstance(context, mockPersistence, mockBehavior); + + int dataSize = 600; + MockReplicatedLogEntry logEntry = new MockReplicatedLogEntry(1, 2, new MockPayload("2", dataSize)); + + doReturn(true).when(mockSnapshotManager).capture(same(logEntry), eq(1L)); + + log.appendAndPersist(logEntry); + verifyPersist(logEntry); + + verify(mockSnapshotManager).capture(same(logEntry), eq(1L)); + + reset(mockPersistence, mockSnapshotManager); + + logEntry = new MockReplicatedLogEntry(1, 3, new MockPayload("3", 5)); + + log.appendAndPersist(logEntry); + verifyPersist(logEntry); + + verifyNoMoreInteractions(mockSnapshotManager); + + assertEquals("size", 2, log.size()); + } + + @Test + public void testRemoveFromAndPersist() throws Exception { + + ReplicatedLog log = ReplicatedLogImpl.newInstance(context, mockPersistence, mockBehavior); + + log.append(new MockReplicatedLogEntry(1, 0, new MockPayload("0"))); + log.append(new MockReplicatedLogEntry(1, 1, new MockPayload("1"))); + log.append(new MockReplicatedLogEntry(1, 2, new MockPayload("2"))); + + log.removeFromAndPersist(1); + + DeleteEntries deleteEntries = new DeleteEntries(1); + verifyPersist(deleteEntries, match(deleteEntries)); + + assertEquals("size", 1, log.size()); + + reset(mockPersistence); + + log.removeFromAndPersist(1); + + verifyNoMoreInteractions(mockPersistence); + } + + public Matcher match(final DeleteEntries actual){ + return new BaseMatcher() { + @Override + public boolean matches(Object o) { + DeleteEntries other = (DeleteEntries) o; + return actual.getFromIndex() == other.getFromIndex(); + } + + @Override + public void describeTo(Description description) { + description.appendText("DeleteEntries: fromIndex: " + actual.getFromIndex()); + } + }; + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicationAndSnapshotsIntegrationTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicationAndSnapshotsIntegrationTest.java index bd670fd581..c74705d13f 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicationAndSnapshotsIntegrationTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicationAndSnapshotsIntegrationTest.java @@ -14,7 +14,7 @@ import java.util.List; import org.junit.Test; import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload; import org.opendaylight.controller.cluster.raft.RaftActor.UpdateElectionTerm; -import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; +import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntries; import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; @@ -36,13 +36,14 @@ public class ReplicationAndSnapshotsIntegrationTest extends AbstractRaftActorInt private MockPayload recoveredPayload0; private MockPayload recoveredPayload1; private MockPayload recoveredPayload2; + private MockPayload payload3; private MockPayload payload4; private MockPayload payload5; private MockPayload payload6; private MockPayload payload7; @Test - public void runTest() { + public void runTest() throws Exception { testLog.info("testReplicationAndSnapshots starting"); // Setup the persistent journal for the leader. We'll start up with 3 journal log entries (one less @@ -55,7 +56,7 @@ public class ReplicationAndSnapshotsIntegrationTest extends AbstractRaftActorInt InMemoryJournal.addEntry(leaderId, seqId++, new ReplicatedLogImplEntry(1, initialTerm, recoveredPayload1)); recoveredPayload2 = new MockPayload("two"); InMemoryJournal.addEntry(leaderId, seqId++, new ReplicatedLogImplEntry(2, initialTerm, recoveredPayload2)); - InMemoryJournal.addEntry(leaderId, seqId++, new ApplyLogEntries(2)); + InMemoryJournal.addEntry(leaderId, seqId++, new ApplyJournalEntries(2)); origLeaderJournal = InMemoryJournal.get(leaderId, ReplicatedLogImplEntry.class); @@ -157,19 +158,21 @@ public class ReplicationAndSnapshotsIntegrationTest extends AbstractRaftActorInt * 4 and we already have 3 entries in the journal log, this should initiate a snapshot. In this * scenario, the follower consensus and application of state is delayed until after the snapshot * completes. + * @throws Exception */ - private void testFirstSnapshot() { + private void testFirstSnapshot() throws Exception { testLog.info("testFirstSnapshot starting"); - byte[] snapshot = new byte[] {1,2,3,4}; - leaderActor.underlyingActor().setSnapshot(snapshot); + expSnapshotState.add(recoveredPayload0); + expSnapshotState.add(recoveredPayload1); + expSnapshotState.add(recoveredPayload2); // Delay the consensus by temporarily dropping the AppendEntries to both followers. follower1Actor.underlyingActor().startDropMessages(AppendEntries.class); follower2Actor.underlyingActor().startDropMessages(AppendEntries.class); // Send the payload. - MockPayload payload3 = sendPayloadData(leaderActor, "three"); + payload3 = sendPayloadData(leaderActor, "three"); // Wait for snapshot complete. MessageCollectorActor.expectFirstMatching(leaderCollectorActor, SaveSnapshotSuccess.class); @@ -185,7 +188,7 @@ public class ReplicationAndSnapshotsIntegrationTest extends AbstractRaftActorInt // the last applied log entry (2) even though the leader hasn't yet advanced its cached snapshot index. List persistedSnapshots = InMemorySnapshotStore.getSnapshots(leaderId, Snapshot.class); assertEquals("Persisted snapshots size", 1, persistedSnapshots.size()); - verifySnapshot("Persisted", persistedSnapshots.get(0), initialTerm, 2, currentTerm, 3, snapshot); + verifySnapshot("Persisted", persistedSnapshots.get(0), initialTerm, 2, currentTerm, 3); List unAppliedEntry = persistedSnapshots.get(0).getUnAppliedEntries(); assertEquals("Persisted Snapshot getUnAppliedEntries size", 1, unAppliedEntry.size()); verifyReplicatedLogEntry(unAppliedEntry.get(0), currentTerm, 3, payload3); @@ -286,12 +289,15 @@ public class ReplicationAndSnapshotsIntegrationTest extends AbstractRaftActorInt /** * Send one more payload to trigger another snapshot. In this scenario, we delay the snapshot until * consensus occurs and the leader applies the state. + * @throws Exception */ - private void testSecondSnapshot() { + private void testSecondSnapshot() throws Exception { testLog.info("testSecondSnapshot starting"); - byte[] snapshot = new byte[] {5,6,7,8}; - leaderActor.underlyingActor().setSnapshot(snapshot); + expSnapshotState.add(payload3); + expSnapshotState.add(payload4); + expSnapshotState.add(payload5); + expSnapshotState.add(payload6); // Delay the CaptureSnapshot message to the leader actor. leaderActor.underlyingActor().startDropMessages(CaptureSnapshot.class); @@ -341,11 +347,14 @@ public class ReplicationAndSnapshotsIntegrationTest extends AbstractRaftActorInt assertEquals("Leader journal last index", 7, leaderContext.getReplicatedLog().lastIndex()); assertEquals("Leader commit index", 7, leaderContext.getCommitIndex()); - // Verify the persisted snapshot. This should reflect the advanced snapshot index as the last applied - // log entry (6). + expSnapshotState.add(payload7); + + // Verify the persisted snapshot. This should reflect the snapshot index as the last applied + // log entry (7) and shouldn't contain any unapplied entries as we capture persisted the snapshot data + // when the snapshot is created (ie when the CaptureSnapshot is processed). List persistedSnapshots = InMemorySnapshotStore.getSnapshots(leaderId, Snapshot.class); assertEquals("Persisted snapshots size", 1, persistedSnapshots.size()); - verifySnapshot("Persisted", persistedSnapshots.get(0), currentTerm, 6, currentTerm, 7, snapshot); + verifySnapshot("Persisted", persistedSnapshots.get(0), currentTerm, 6, currentTerm, 7); List unAppliedEntry = persistedSnapshots.get(0).getUnAppliedEntries(); assertEquals("Persisted Snapshot getUnAppliedEntries size", 1, unAppliedEntry.size()); verifyReplicatedLogEntry(unAppliedEntry.get(0), currentTerm, 7, payload7); @@ -404,6 +413,8 @@ public class ReplicationAndSnapshotsIntegrationTest extends AbstractRaftActorInt leaderActor.underlyingActor().waitForRecoveryComplete(); + leaderContext = leaderActor.underlyingActor().getRaftActorContext(); + assertEquals("Leader snapshot term", currentTerm, leaderContext.getReplicatedLog().getSnapshotTerm()); assertEquals("Leader snapshot index", 6, leaderContext.getReplicatedLog().getSnapshotIndex()); assertEquals("Leader journal log size", 1, leaderContext.getReplicatedLog().size()); diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest.java index d4a9f7701b..ff9b8ce630 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest.java @@ -7,7 +7,6 @@ */ package org.opendaylight.controller.cluster.raft; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import akka.persistence.SaveSnapshotSuccess; import com.google.common.collect.ImmutableMap; @@ -185,6 +184,10 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A MessageCollectorActor.clearMessages(follower1CollectorActor); MessageCollectorActor.clearMessages(follower2CollectorActor); + expSnapshotState.add(payload0); + expSnapshotState.add(payload1); + expSnapshotState.add(payload2); + testLog.info("testInitialReplications complete"); } @@ -198,8 +201,6 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A testLog.info("testSubsequentReplicationsAndSnapshots starting: sending first payload, replicatedToAllIndex: {}", leader.getReplicatedToAllIndex()); - leaderActor.underlyingActor().setSnapshot(new byte[] {2}); - follower2Actor.underlyingActor().startDropMessages(AppendEntries.class); // Send the first payload - this should cause the first snapshot. @@ -207,8 +208,7 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A MessageCollectorActor.expectFirstMatching(leaderCollectorActor, SaveSnapshotSuccess.class); - byte[] snapshot = new byte[] {6}; - leaderActor.underlyingActor().setSnapshot(snapshot); + expSnapshotState.add(payload3); testLog.info("testSubsequentReplicationsAndSnapshots: sending 4 more payloads"); @@ -273,7 +273,7 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A // Verify the leader's persisted snapshot. List persistedSnapshots = InMemorySnapshotStore.getSnapshots(leaderId, Snapshot.class); assertEquals("Persisted snapshots size", 1, persistedSnapshots.size()); - verifySnapshot("Persisted", persistedSnapshots.get(0), currentTerm, 3, currentTerm, 7, snapshot); + verifySnapshot("Persisted", persistedSnapshots.get(0), currentTerm, 3, currentTerm, 7); List unAppliedEntry = persistedSnapshots.get(0).getUnAppliedEntries(); assertEquals("Persisted Snapshot getUnAppliedEntries size", 4, unAppliedEntry.size()); verifyReplicatedLogEntry(unAppliedEntry.get(0), currentTerm, 4, payload4); @@ -313,6 +313,11 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A MessageCollectorActor.clearMessages(follower1CollectorActor); MessageCollectorActor.clearMessages(follower2CollectorActor); + expSnapshotState.add(payload4); + expSnapshotState.add(payload5); + expSnapshotState.add(payload6); + expSnapshotState.add(payload7); + testLog.info("testSubsequentReplicationsAndSnapshots complete"); } @@ -327,8 +332,6 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A leader.getReplicatedToAllIndex()); leaderActor.underlyingActor().setMockTotalMemory(1000); - byte[] snapshot = new byte[] {6}; - leaderActor.underlyingActor().setSnapshot(snapshot); // We'll expect a ReplicatedLogImplEntry message and an ApplyJournalEntries message added to the journal. InMemoryJournal.addWriteMessagesCompleteLatch(leaderId, 2); @@ -351,6 +354,8 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A CaptureSnapshot captureSnapshot = MessageCollectorActor.getFirstMatching(leaderCollectorActor, CaptureSnapshot.class); Assert.assertNull("Leader received unexpected CaptureSnapshot", captureSnapshot); + expSnapshotState.add(payload8); + // Send another payload with a large enough relative size in combination with the last payload // that exceeds the memory threshold (70% * 1000 = 700) - this should do a snapshot. payload9 = sendPayloadData(leaderActor, "nine", 201); @@ -383,7 +388,7 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A // Verify the leader's persisted snapshot. List persistedSnapshots = InMemorySnapshotStore.getSnapshots(leaderId, Snapshot.class); assertEquals("Persisted snapshots size", 1, persistedSnapshots.size()); - verifySnapshot("Persisted", persistedSnapshots.get(0), currentTerm, 8, currentTerm, 9, snapshot); + verifySnapshot("Persisted", persistedSnapshots.get(0), currentTerm, 8, currentTerm, 9); List unAppliedEntry = persistedSnapshots.get(0).getUnAppliedEntries(); assertEquals("Persisted Snapshot getUnAppliedEntries size", 1, unAppliedEntry.size()); verifyReplicatedLogEntry(unAppliedEntry.get(0), currentTerm, 9, payload9); @@ -451,6 +456,8 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A MessageCollectorActor.clearMessages(leaderCollectorActor); MessageCollectorActor.clearMessages(follower1CollectorActor); MessageCollectorActor.clearMessages(follower2CollectorActor); + + expSnapshotState.add(payload10); } /** @@ -467,8 +474,7 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A InstallSnapshot installSnapshot; InstallSnapshotReply installSnapshotReply; - byte[] snapshot = new byte[] {10}; - leaderActor.underlyingActor().setSnapshot(snapshot); + expSnapshotState.add(payload9); // Now stop dropping AppendEntries in follower 2. follower2Actor.underlyingActor().stopDropMessages(AppendEntries.class); @@ -480,7 +486,7 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A assertEquals("InstallSnapshot getTotalChunks", 1, installSnapshot.getTotalChunks()); assertEquals("InstallSnapshot getLastIncludedTerm", currentTerm, installSnapshot.getLastIncludedTerm()); assertEquals("InstallSnapshot getLastIncludedIndex", 8, installSnapshot.getLastIncludedIndex()); - assertArrayEquals("InstallSnapshot getData", snapshot, installSnapshot.getData().toByteArray()); + //assertArrayEquals("InstallSnapshot getData", snapshot, installSnapshot.getData().toByteArray()); installSnapshotReply = MessageCollectorActor.expectFirstMatching(leaderCollectorActor, InstallSnapshotReply.class); assertEquals("InstallSnapshotReply getTerm", currentTerm, installSnapshotReply.getTerm()); @@ -490,7 +496,7 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A // Verify follower 2 applies the snapshot. applySnapshot = MessageCollectorActor.expectFirstMatching(follower2CollectorActor, ApplySnapshot.class); - verifySnapshot("Follower 2", applySnapshot.getSnapshot(), currentTerm, 8, currentTerm, 8, snapshot); + verifySnapshot("Follower 2", applySnapshot.getSnapshot(), currentTerm, 8, currentTerm, 8); assertEquals("Persisted Snapshot getUnAppliedEntries size", 0, applySnapshot.getSnapshot().getUnAppliedEntries().size()); // Verify follower 2 only applies the second log entry (9) as the first one (8) was in the snapshot. @@ -523,7 +529,7 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A persistedSnapshots = InMemorySnapshotStore.getSnapshots(leaderId, Snapshot.class); Assert.assertTrue("Expected at least 1 persisted snapshots", persistedSnapshots.size() > 0); Snapshot persistedSnapshot = persistedSnapshots.get(persistedSnapshots.size() - 1); - verifySnapshot("Persisted", persistedSnapshot, currentTerm, 9, currentTerm, 9, snapshot); + verifySnapshot("Persisted", persistedSnapshot, currentTerm, 9, currentTerm, 9); unAppliedEntry = persistedSnapshot.getUnAppliedEntries(); assertEquals("Persisted Snapshot getUnAppliedEntries size", 0, unAppliedEntry.size()); @@ -535,16 +541,14 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A /** * Do another round of payloads and snapshot to verify replicatedToAllIndex gets back on track and * snapshots works as expected after doing a follower snapshot. In this step we don't lag a follower. + * @throws Exception */ - private void testFinalReplicationsAndSnapshot() { + private void testFinalReplicationsAndSnapshot() throws Exception { List applyStates; ApplyState applyState; testLog.info("testFinalReplicationsAndSnapshot starting: replicatedToAllIndex: {}", leader.getReplicatedToAllIndex()); - byte[] snapshot = new byte[] {14}; - leaderActor.underlyingActor().setSnapshot(snapshot); - // Send another payload - a snapshot should occur. payload11 = sendPayloadData(leaderActor, "eleven"); @@ -557,7 +561,7 @@ public class ReplicationAndSnapshotsWithLaggingFollowerIntegrationTest extends A // Verify the leader's last persisted snapshot (previous ones may not be purged yet). List persistedSnapshots = InMemorySnapshotStore.getSnapshots(leaderId, Snapshot.class); Snapshot persistedSnapshot = persistedSnapshots.get(persistedSnapshots.size() - 1); - verifySnapshot("Persisted", persistedSnapshot, currentTerm, 10, currentTerm, 11, snapshot); + verifySnapshot("Persisted", persistedSnapshot, currentTerm, 10, currentTerm, 11); List unAppliedEntry = persistedSnapshot.getUnAppliedEntries(); assertEquals("Persisted Snapshot getUnAppliedEntries size", 1, unAppliedEntry.size()); verifyReplicatedLogEntry(unAppliedEntry.get(0), currentTerm, 11, payload11); diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/SnapshotManagerTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/SnapshotManagerTest.java index 5a0d5aed74..8ab762f786 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/SnapshotManagerTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/SnapshotManagerTest.java @@ -69,6 +69,7 @@ public class SnapshotManagerTest extends AbstractActorTest { doReturn(10L).when(mockConfigParams).getSnapshotBatchCount(); doReturn(mockReplicatedLog).when(mockRaftActorContext).getReplicatedLog(); doReturn("123").when(mockRaftActorContext).getId(); + doReturn(mockDataPersistenceProvider).when(mockRaftActorContext).getPersistenceProvider(); doReturn("123").when(mockRaftActorBehavior).getLeaderId(); ElectionTerm mockElectionTerm = mock(ElectionTerm.class); @@ -384,6 +385,8 @@ public class SnapshotManagerTest extends AbstractActorTest { @Test public void testCommit(){ + doReturn(50L).when(mockDataPersistenceProvider).getLastSequenceNumber(); + // when replicatedToAllIndex = -1 snapshotManager.captureToInstall(new MockRaftActorContext.MockReplicatedLogEntry(6, 9, new MockRaftActorContext.MockPayload()), -1, "follower-1"); @@ -397,7 +400,7 @@ public class SnapshotManagerTest extends AbstractActorTest { verify(mockReplicatedLog).snapshotCommit(); - verify(mockDataPersistenceProvider).deleteMessages(100L); + verify(mockDataPersistenceProvider).deleteMessages(50L); ArgumentCaptor criteriaCaptor = ArgumentCaptor.forClass(SnapshotSelectionCriteria.class); @@ -438,6 +441,8 @@ public class SnapshotManagerTest extends AbstractActorTest { @Test public void testCallingCommitMultipleTimesCausesNoHarm(){ + doReturn(50L).when(mockDataPersistenceProvider).getLastSequenceNumber(); + // when replicatedToAllIndex = -1 snapshotManager.captureToInstall(new MockRaftActorContext.MockReplicatedLogEntry(6, 9, new MockRaftActorContext.MockPayload()), -1, "follower-1"); @@ -453,7 +458,7 @@ public class SnapshotManagerTest extends AbstractActorTest { verify(mockReplicatedLog, times(1)).snapshotCommit(); - verify(mockDataPersistenceProvider, times(1)).deleteMessages(100L); + verify(mockDataPersistenceProvider, times(1)).deleteMessages(50L); verify(mockDataPersistenceProvider, times(1)).deleteSnapshots(any(SnapshotSelectionCriteria.class)); } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/base/messages/DeleteEntriesTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/base/messages/DeleteEntriesTest.java new file mode 100644 index 0000000000..55d2bcc688 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/base/messages/DeleteEntriesTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft.base.messages; + +import org.apache.commons.lang.SerializationUtils; +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit tests for DeleteEntries. + * + * @author Thomas Pantelis + */ +public class DeleteEntriesTest { + + @Test + public void testSerialization() { + + DeleteEntries deleteEntries = new DeleteEntries(11); + + DeleteEntries clone = (DeleteEntries) SerializationUtils.clone(deleteEntries); + + Assert.assertEquals("getFromIndex", 11, clone.getFromIndex()); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/InMemoryJournal.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/InMemoryJournal.java index 0737d75a7f..d482e28401 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/InMemoryJournal.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/InMemoryJournal.java @@ -216,6 +216,7 @@ public class InMemoryJournal extends AsyncWriteJournal { @Override public Future doAsyncDeleteMessagesTo(String persistenceId, long toSequenceNr, boolean permanent) { + LOG.trace("doAsyncDeleteMessagesTo: {}", toSequenceNr); Map journal = journals.get(persistenceId); if(journal != null) { synchronized (journal) { diff --git a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/md/sal/binding/api/DataObjectModification.java b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/md/sal/binding/api/DataObjectModification.java index 678ac34e39..3dc6e4030f 100644 --- a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/md/sal/binding/api/DataObjectModification.java +++ b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/md/sal/binding/api/DataObjectModification.java @@ -65,7 +65,17 @@ public interface DataObjectModification extends org.openda @Nonnull ModificationType getModificationType(); /** - * Returns after state of top level container. + * Returns before-state of top level container. Implementations are encouraged, + * but not required to provide this state. + * + * @param root Class representing data container + * @return State of object before modification. Null if subtree was not present, + * or the implementation cannot provide the state. + */ + @Nullable T getDataBefore(); + + /** + * Returns after-state of top level container. * * @param root Class representing data container * @return State of object after modification. Null if subtree is not present. diff --git a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/md/sal/binding/api/DataTreeIdentifier.java b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/md/sal/binding/api/DataTreeIdentifier.java index c1c23d5e6f..b86d31b790 100644 --- a/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/md/sal/binding/api/DataTreeIdentifier.java +++ b/opendaylight/md-sal/sal-binding-api/src/main/java/org/opendaylight/controller/md/sal/binding/api/DataTreeIdentifier.java @@ -44,7 +44,7 @@ public final class DataTreeIdentifier implements Immutable * * @return Instance identifier corresponding to the root node. */ - public @Nonnull InstanceIdentifier getRootIdentifier() { + public @Nonnull InstanceIdentifier getRootIdentifier() { return rootIdentifier; } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMNotificationListenerAdapter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMNotificationListenerAdapter.java new file mode 100644 index 0000000000..03da29642c --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMNotificationListenerAdapter.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.binding.impl; + +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.dom.api.DOMNotification; +import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener; +import org.opendaylight.controller.sal.binding.spi.NotificationInvokerFactory; +import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer; +import org.opendaylight.yangtools.yang.binding.Notification; + +class BindingDOMNotificationListenerAdapter implements DOMNotificationListener { + + private final NotificationInvokerFactory.NotificationInvoker invoker; + private final BindingNormalizedNodeSerializer codec; + + public BindingDOMNotificationListenerAdapter(final BindingNormalizedNodeSerializer codec, final NotificationInvokerFactory.NotificationInvoker invoker) { + this.codec = codec; + this.invoker = invoker; + } + + @Override + public void onNotification(@Nonnull final DOMNotification notification) { + final Notification baNotification = + codec.fromNormalizedNodeNotification(notification.getType(), notification.getBody()); + invoker.getInvocationProxy().onNotification(baNotification); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMNotificationServiceAdapter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMNotificationServiceAdapter.java index 50a0a2815f..6006266cc2 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMNotificationServiceAdapter.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMNotificationServiceAdapter.java @@ -13,10 +13,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; -import javax.annotation.Nonnull; import org.opendaylight.controller.md.sal.binding.api.NotificationService; import org.opendaylight.controller.md.sal.binding.impl.BindingDOMAdapterBuilder.Factory; -import org.opendaylight.controller.md.sal.dom.api.DOMNotification; import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener; import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService; import org.opendaylight.controller.md.sal.dom.api.DOMService; @@ -44,16 +42,16 @@ public class BindingDOMNotificationServiceAdapter implements NotificationService private final DOMNotificationService domNotifService; private final NotificationInvokerFactory notificationInvokerFactory; - public BindingDOMNotificationServiceAdapter(BindingNormalizedNodeSerializer codec, DOMNotificationService domNotifService, NotificationInvokerFactory notificationInvokerFactory) { + public BindingDOMNotificationServiceAdapter(final BindingNormalizedNodeSerializer codec, final DOMNotificationService domNotifService, final NotificationInvokerFactory notificationInvokerFactory) { this.codec = codec; this.domNotifService = domNotifService; this.notificationInvokerFactory = notificationInvokerFactory; } @Override - public ListenerRegistration registerNotificationListener(T listener) { + public ListenerRegistration registerNotificationListener(final T listener) { final NotificationInvokerFactory.NotificationInvoker invoker = notificationInvokerFactory.invokerFor(listener); - final DOMNotificationListener domListener = new NotificationInvokerImpl(invoker); + final DOMNotificationListener domListener = new BindingDOMNotificationListenerAdapter(codec, invoker); final Collection schemaPaths = convertNotifTypesToSchemaPath(invoker.getSupportedNotifications()); final ListenerRegistration domRegistration = domNotifService.registerNotificationListener(domListener, schemaPaths); @@ -62,9 +60,9 @@ public class BindingDOMNotificationServiceAdapter implements NotificationService - private Collection convertNotifTypesToSchemaPath(Set> notificationTypes) { + private Collection convertNotifTypesToSchemaPath(final Set> notificationTypes) { final List schemaPaths = new ArrayList<>(); - for (Class notificationType : notificationTypes) { + for (final Class notificationType : notificationTypes) { schemaPaths.add(SchemaPath.create(true, BindingReflections.findQName(notificationType))); } return schemaPaths; @@ -78,7 +76,7 @@ public class BindingDOMNotificationServiceAdapter implements NotificationService private static class ListenerRegistrationImpl extends AbstractListenerRegistration { private final ListenerRegistration listenerRegistration; - public ListenerRegistrationImpl(T listener, ListenerRegistration listenerRegistration) { + public ListenerRegistrationImpl(final T listener, final ListenerRegistration listenerRegistration) { super(listener); this.listenerRegistration = listenerRegistration; } @@ -89,30 +87,14 @@ public class BindingDOMNotificationServiceAdapter implements NotificationService } } - private class NotificationInvokerImpl implements DOMNotificationListener { - private final NotificationInvokerFactory.NotificationInvoker invoker; - - public NotificationInvokerImpl(NotificationInvokerFactory.NotificationInvoker invoker) { - this.invoker = invoker; - } - - @Override - public void onNotification(@Nonnull DOMNotification notification) { - final Notification baNotification = - codec.fromNormalizedNodeNotification(notification.getType(), notification.getBody()); - invoker.getInvocationProxy().onNotification(baNotification); - - } - } - private static class Builder extends BindingDOMAdapterBuilder { @Override - protected NotificationService createInstance(BindingToNormalizedNodeCodec codec, - ClassToInstanceMap delegates) { - DOMNotificationService domNotification = delegates.getInstance(DOMNotificationService.class); - NotificationInvokerFactory invokerFactory = SingletonHolder.INVOKER_FACTORY; + protected NotificationService createInstance(final BindingToNormalizedNodeCodec codec, + final ClassToInstanceMap delegates) { + final DOMNotificationService domNotification = delegates.getInstance(DOMNotificationService.class); + final NotificationInvokerFactory invokerFactory = SingletonHolder.INVOKER_FACTORY; return new BindingDOMNotificationServiceAdapter(codec.getCodecRegistry(), domNotification, invokerFactory); } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java index a165242b30..83d48f77a0 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazyDataObjectModification.java @@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory; * * @param Type of Binding Data Object */ -class LazyDataObjectModification implements DataObjectModification { +final class LazyDataObjectModification implements DataObjectModification { private final static Logger LOG = LoggerFactory.getLogger(LazyDataObjectModification.class); @@ -57,7 +57,7 @@ class LazyDataObjectModification implements DataObjectModi return new LazyDataObjectModification<>(codec,domData); } - static Collection> from(final BindingCodecTreeNode parentCodec, + private static Collection> from(final BindingCodecTreeNode parentCodec, final Collection domChildNodes) { final ArrayList> result = new ArrayList<>(domChildNodes.size()); populateList(result, parentCodec, domChildNodes); @@ -79,7 +79,7 @@ class LazyDataObjectModification implements DataObjectModi parentCodec.yangPathArgumentChild(domChildNode.getIdentifier()); populateList(result,type, childCodec, domChildNode); } catch (final IllegalArgumentException e) { - if(type == BindingStructuralType.UNKNOWN) { + if (type == BindingStructuralType.UNKNOWN) { LOG.debug("Unable to deserialize unknown DOM node {}",domChildNode,e); } else { LOG.debug("Binding representation for DOM node {} was not found",domChildNode,e); @@ -89,7 +89,6 @@ class LazyDataObjectModification implements DataObjectModi } } - private static void populateList(final List> result, final BindingStructuralType type, final BindingCodecTreeNode childCodec, final DataTreeCandidateNode domChildNode) { @@ -116,6 +115,11 @@ class LazyDataObjectModification implements DataObjectModi } } + @Override + public T getDataBefore() { + return deserialize(domData.getDataBefore()); + } + @Override public T getDataAfter() { return deserialize(domData.getDataAfter()); @@ -149,8 +153,8 @@ class LazyDataObjectModification implements DataObjectModi @Override public Collection> getModifiedChildren() { - if(childNodesCache == null) { - childNodesCache = from(codec,domData.getChildNodes()); + if (childNodesCache == null) { + childNodesCache = from(codec, domData.getChildNodes()); } return childNodesCache; } @@ -191,7 +195,7 @@ class LazyDataObjectModification implements DataObjectModi } private T deserialize(final Optional> dataAfter) { - if(dataAfter.isPresent()) { + if (dataAfter.isPresent()) { return codec.deserialize(dataAfter.get()); } return null; diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/AbstractDataServiceTest.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/AbstractDataServiceTest.java index 6de2666de1..7acdafef24 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/AbstractDataServiceTest.java +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/AbstractDataServiceTest.java @@ -14,7 +14,6 @@ import org.opendaylight.controller.sal.binding.api.data.DataProviderService; import org.opendaylight.controller.sal.binding.test.util.BindingBrokerTestFactory; import org.opendaylight.controller.sal.binding.test.util.BindingTestContext; -@SuppressWarnings("deprecation") public abstract class AbstractDataServiceTest { protected DataProviderService baDataService; diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java index 3d25018e24..cdf7fbc7f9 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java @@ -9,10 +9,8 @@ package org.opendaylight.controller.md.sal.binding.data; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; - import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; - import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; @@ -28,14 +26,13 @@ import org.opendaylight.yangtools.yang.common.RpcResult; * FIXME: THis test should be moved to sal-binding-broker and rewriten * to use new DataBroker API */ -@SuppressWarnings("deprecation") public class ConcurrentImplicitCreateTest extends AbstractDataServiceTest { private static final TopLevelListKey FOO_KEY = new TopLevelListKey("foo"); private static final TopLevelListKey BAR_KEY = new TopLevelListKey("bar"); - private static InstanceIdentifier TOP_PATH = InstanceIdentifier.builder(Top.class).build(); - private static InstanceIdentifier FOO_PATH = TOP_PATH.child(TopLevelList.class, FOO_KEY); - private static InstanceIdentifier BAR_PATH = TOP_PATH.child(TopLevelList.class, BAR_KEY); + private static final InstanceIdentifier TOP_PATH = InstanceIdentifier.builder(Top.class).build(); + private static final InstanceIdentifier FOO_PATH = TOP_PATH.child(TopLevelList.class, FOO_KEY); + private static final InstanceIdentifier BAR_PATH = TOP_PATH.child(TopLevelList.class, BAR_KEY); @Test public void testConcurrentCreate() throws InterruptedException, ExecutionException { diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java index 0a611a75d0..e0a151adc0 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java @@ -10,12 +10,11 @@ package org.opendaylight.controller.md.sal.binding.data; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; - +import com.google.common.util.concurrent.SettableFuture; import java.util.Collections; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; @@ -35,12 +34,9 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controll import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import com.google.common.util.concurrent.SettableFuture; - /** * FIXME: THis test should be moved to compat test-suite */ -@SuppressWarnings("deprecation") public class WildcardedDataChangeListenerTest extends AbstractDataServiceTest { private static final TopLevelListKey TOP_LEVEL_LIST_0_KEY = new TopLevelListKey("test:0"); diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DeleteNestedAugmentationListenParentTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DeleteNestedAugmentationListenParentTest.java index 40d4591001..73712813d4 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DeleteNestedAugmentationListenParentTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DeleteNestedAugmentationListenParentTest.java @@ -1,9 +1,8 @@ package org.opendaylight.controller.sal.binding.test.bugfix; import static org.junit.Assert.assertFalse; - +import com.google.common.util.concurrent.SettableFuture; import java.util.concurrent.ExecutionException; - import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; @@ -23,9 +22,6 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controll import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import com.google.common.util.concurrent.SettableFuture; - -@SuppressWarnings("deprecation") public class DeleteNestedAugmentationListenParentTest extends AbstractDataServiceTest { private static final TopLevelListKey FOO_KEY = new TopLevelListKey("foo"); diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java index 0f9051d41c..591effbe03 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java @@ -10,9 +10,8 @@ package org.opendaylight.controller.sal.binding.test.bugfix; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; - +import com.google.common.util.concurrent.SettableFuture; import java.util.concurrent.TimeUnit; - import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; @@ -29,9 +28,6 @@ import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import com.google.common.util.concurrent.SettableFuture; - -@SuppressWarnings("deprecation") public class WriteParentListenAugmentTest extends AbstractDataServiceTest { private static final String TLL_NAME = "foo"; diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentReadChildTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentReadChildTest.java index 7941f4d4ae..de7445ee70 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentReadChildTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentReadChildTest.java @@ -11,7 +11,7 @@ package org.opendaylight.controller.sal.binding.test.bugfix; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; - +import com.google.common.collect.ImmutableList; import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; @@ -30,9 +30,6 @@ import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; -import com.google.common.collect.ImmutableList; - -@SuppressWarnings("deprecation") public class WriteParentReadChildTest extends AbstractDataServiceTest { private static final int LIST11_ID = 1234; diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/BrokerIntegrationTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/BrokerIntegrationTest.java index 48027114d7..425a62be2d 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/BrokerIntegrationTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/BrokerIntegrationTest.java @@ -10,9 +10,7 @@ package org.opendaylight.controller.sal.binding.test.connect.dom; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; - import java.util.concurrent.Future; - import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; @@ -25,10 +23,6 @@ import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; -/** - * FIXME: Migrate to use new Data Broker APIs - */ -@SuppressWarnings("deprecation") public class BrokerIntegrationTest extends AbstractDataServiceTest { private static final TopLevelListKey TLL_FOO_KEY = new TopLevelListKey("foo"); diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerMountPointTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerMountPointTest.java index 1be550f82f..23e6053a1f 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerMountPointTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerMountPointTest.java @@ -56,7 +56,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; -@SuppressWarnings("deprecation") public class CrossBrokerMountPointTest { private static final QName TLL_NAME_QNAME = QName.create(TopLevelList.QNAME, "name"); diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/DOMRpcServiceTestBugfix560.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/DOMRpcServiceTestBugfix560.java index a0f4e99a6b..1e93c59434 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/DOMRpcServiceTestBugfix560.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/DOMRpcServiceTestBugfix560.java @@ -10,7 +10,6 @@ package org.opendaylight.controller.sal.binding.test.connect.dom; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; - import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; @@ -97,12 +96,8 @@ public class DOMRpcServiceTestBugfix560 { assertNotNull(moduleStream); final List rpcModels = Collections.singletonList(moduleStream); - @SuppressWarnings("deprecation") - final - Set modules = parser.parseYangModelsFromStreams(rpcModels); - @SuppressWarnings("deprecation") - final - SchemaContext mountSchemaContext = parser.resolveSchemaContext(modules); + final Set modules = parser.parseYangModelsFromStreams(rpcModels); + final SchemaContext mountSchemaContext = parser.resolveSchemaContext(modules); schemaContext = mountSchemaContext; } @@ -121,7 +116,6 @@ public class DOMRpcServiceTestBugfix560 { .child(TopLevelList.class, new TopLevelListKey(mount)).toInstance(); } - @SuppressWarnings("deprecation") @Test public void test() throws ExecutionException, InterruptedException { // FIXME: This is made to only make sure instance identifier codec diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/DataPersistenceProvider.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/DataPersistenceProvider.java index db4bf31438..730310e22e 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/DataPersistenceProvider.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/DataPersistenceProvider.java @@ -52,4 +52,8 @@ public interface DataPersistenceProvider { */ void deleteMessages(long sequenceNumber); + /** + * Returns the last sequence number contained in the journal. + */ + long getLastSequenceNumber(); } diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/DelegatingPersistentDataProvider.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/DelegatingPersistentDataProvider.java index c74236bb47..e27fa26aeb 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/DelegatingPersistentDataProvider.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/DelegatingPersistentDataProvider.java @@ -54,4 +54,9 @@ public class DelegatingPersistentDataProvider implements DataPersistenceProvider public void deleteMessages(long sequenceNumber) { delegate.deleteMessages(sequenceNumber); } + + @Override + public long getLastSequenceNumber() { + return delegate.getLastSequenceNumber(); + } } diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/NonPersistentDataProvider.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/NonPersistentDataProvider.java index fed81177a1..d1af58f18b 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/NonPersistentDataProvider.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/NonPersistentDataProvider.java @@ -43,4 +43,9 @@ public class NonPersistentDataProvider implements DataPersistenceProvider { @Override public void deleteMessages(long sequenceNumber) { } + + @Override + public long getLastSequenceNumber() { + return -1; + } } \ No newline at end of file diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/PersistentDataProvider.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/PersistentDataProvider.java index f130a1f27e..4ccd5f4d29 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/PersistentDataProvider.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/PersistentDataProvider.java @@ -47,4 +47,9 @@ public class PersistentDataProvider implements DataPersistenceProvider { public void deleteMessages(long sequenceNumber) { persistentActor.deleteMessages(sequenceNumber); } + + @Override + public long getLastSequenceNumber() { + return persistentActor.lastSequenceNr(); + } } \ No newline at end of file diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractConfig.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractConfig.java index 3a66aa1181..b009fbbdb1 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractConfig.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractConfig.java @@ -20,7 +20,7 @@ public abstract class AbstractConfig implements UnifiedConfig { return config; } - public static abstract class Builder{ + public static abstract class Builder> { protected Map configHolder; protected Config fallback; diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/CommonConfig.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/CommonConfig.java index 48afe40607..746ef4ebb1 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/CommonConfig.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/CommonConfig.java @@ -89,7 +89,7 @@ public class CommonConfig extends AbstractConfig { return cachedMailBoxPushTimeout; } - public static class Builder extends AbstractConfig.Builder{ + public static class Builder> extends AbstractConfig.Builder{ public Builder(String actorSystemName) { super(actorSystemName); diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/DataPersistenceProviderMonitor.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/DataPersistenceProviderMonitor.java index 33d4056395..bad9fc31f4 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/DataPersistenceProviderMonitor.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/DataPersistenceProviderMonitor.java @@ -10,9 +10,8 @@ package org.opendaylight.controller.cluster.datastore; import akka.japi.Procedure; import akka.persistence.SnapshotSelectionCriteria; -import org.opendaylight.controller.cluster.DataPersistenceProvider; - import java.util.concurrent.CountDownLatch; +import org.opendaylight.controller.cluster.DataPersistenceProvider; /** * This class is intended for testing purposes. It just triggers CountDownLatch's in each method. @@ -65,4 +64,9 @@ public class DataPersistenceProviderMonitor implements DataPersistenceProvider { public void setDeleteMessagesLatch(CountDownLatch deleteMessagesLatch) { this.deleteMessagesLatch = deleteMessagesLatch; } + + @Override + public long getLastSequenceNumber() { + return -1; + } } diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/serialization/NormalizedNodeSerializer.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/serialization/NormalizedNodeSerializer.java index fc1bd4225d..eac4fc496f 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/serialization/NormalizedNodeSerializer.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/serialization/NormalizedNodeSerializer.java @@ -467,7 +467,7 @@ public class NormalizedNodeSerializer { return builder.build(); } - private NormalizedNode buildDataContainer(DataContainerNodeBuilder builder, NormalizedNodeMessages.Node node){ + private NormalizedNode buildDataContainer(DataContainerNodeBuilder builder, NormalizedNodeMessages.Node node){ for(NormalizedNodeMessages.Node child : node.getChildList()){ builder.withChild((DataContainerChild) deSerialize(child)); diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/raft/protobuff/client/messages/CompositeModificationByteStringPayload.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/raft/protobuff/client/messages/CompositeModificationByteStringPayload.java index 83e10cf6af..b61b276d5e 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/raft/protobuff/client/messages/CompositeModificationByteStringPayload.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/raft/protobuff/client/messages/CompositeModificationByteStringPayload.java @@ -47,9 +47,9 @@ public class CompositeModificationByteStringPayload extends Payload implements @Override - public Map encode() { + public Map, PersistentMessages.CompositeModification> encode() { Preconditions.checkState(byteString!=null); - Map map = new HashMap<>(); + Map, PersistentMessages.CompositeModification> map = new HashMap<>(); map.put(org.opendaylight.controller.protobuff.messages.shard.CompositeModificationPayload.modification, getModificationInternal()); return map; diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/raft/protobuff/client/messages/CompositeModificationPayload.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/raft/protobuff/client/messages/CompositeModificationPayload.java index fe5043e73d..cef20af650 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/raft/protobuff/client/messages/CompositeModificationPayload.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/raft/protobuff/client/messages/CompositeModificationPayload.java @@ -31,9 +31,9 @@ public class CompositeModificationPayload extends Payload implements this.modification = (PersistentMessages.CompositeModification) Preconditions.checkNotNull(modification, "modification should not be null"); } - @Override public Map encode() { + @Override public Map, PersistentMessages.CompositeModification> encode() { Preconditions.checkState(modification!=null); - Map map = new HashMap<>(); + Map, PersistentMessages.CompositeModification> map = new HashMap<>(); map.put( org.opendaylight.controller.protobuff.messages.shard.CompositeModificationPayload.modification, this.modification); return map; diff --git a/opendaylight/md-sal/sal-distributed-datastore/pom.xml b/opendaylight/md-sal/sal-distributed-datastore/pom.xml index 10ea2be2dd..27788fc2ee 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/pom.xml +++ b/opendaylight/md-sal/sal-distributed-datastore/pom.xml @@ -92,12 +92,15 @@ org.opendaylight.controller sal-akka-raft - 1.2.0-SNAPSHOT + + + org.opendaylight.controller + sal-akka-raft-example + test org.opendaylight.controller sal-akka-raft - 1.2.0-SNAPSHOT test-jar test diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/AbstractTransactionContext.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/AbstractTransactionContext.java index d94e1c691e..81605d8c8f 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/AbstractTransactionContext.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/AbstractTransactionContext.java @@ -7,40 +7,17 @@ */ package org.opendaylight.controller.cluster.datastore; -import com.google.common.collect.ImmutableList; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; import org.opendaylight.controller.cluster.datastore.identifiers.TransactionIdentifier; -import scala.concurrent.Future; abstract class AbstractTransactionContext implements TransactionContext { - private final List> recordedOperationFutures = new ArrayList<>(); private final TransactionIdentifier identifier; protected AbstractTransactionContext(TransactionIdentifier identifier) { this.identifier = identifier; } - @Override - public final void copyRecordedOperationFutures(Collection> target) { - target.addAll(recordedOperationFutures); - } - protected final TransactionIdentifier getIdentifier() { return identifier; } - - protected final Collection> copyRecordedOperationFutures() { - return ImmutableList.copyOf(recordedOperationFutures); - } - - protected final int recordedOperationCount() { - return recordedOperationFutures.size(); - } - - protected final void recordOperationFuture(Future future) { - recordedOperationFutures.add(future); - } -} +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java index 9cd52b219a..7110adc625 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java @@ -45,7 +45,6 @@ import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransacti import org.opendaylight.controller.cluster.datastore.messages.CloseTransactionChain; import org.opendaylight.controller.cluster.datastore.messages.CommitTransaction; import org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply; -import org.opendaylight.controller.cluster.datastore.messages.CreateSnapshot; import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction; import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply; import org.opendaylight.controller.cluster.datastore.messages.ForwardedReadyTransaction; @@ -59,22 +58,18 @@ import org.opendaylight.controller.cluster.datastore.modification.ModificationPa import org.opendaylight.controller.cluster.datastore.modification.MutableCompositeModification; import org.opendaylight.controller.cluster.datastore.utils.Dispatchers; import org.opendaylight.controller.cluster.datastore.utils.MessageTracker; -import org.opendaylight.controller.cluster.datastore.utils.SerializationUtils; import org.opendaylight.controller.cluster.notifications.RegisterRoleChangeListener; import org.opendaylight.controller.cluster.notifications.RoleChangeNotifier; import org.opendaylight.controller.cluster.raft.RaftActor; +import org.opendaylight.controller.cluster.raft.RaftActorRecoveryCohort; +import org.opendaylight.controller.cluster.raft.RaftActorSnapshotCohort; import org.opendaylight.controller.cluster.raft.base.messages.FollowerInitialSyncUpStatus; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationByteStringPayload; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationPayload; -import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore; import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStoreFactory; -import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; -import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction; import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import scala.concurrent.duration.Duration; import scala.concurrent.duration.FiniteDuration; @@ -87,8 +82,6 @@ import scala.concurrent.duration.FiniteDuration; */ public class Shard extends RaftActor { - private static final YangInstanceIdentifier DATASTORE_ROOT = YangInstanceIdentifier.builder().build(); - private static final Object TX_COMMIT_TIMEOUT_CHECK_MESSAGE = "txCommitTimeoutCheck"; @VisibleForTesting @@ -104,10 +97,6 @@ public class Shard extends RaftActor { private DatastoreContext datastoreContext; - private SchemaContext schemaContext; - - private int createSnapshotTransactionCounter; - private final ShardCommitCoordinator commitCoordinator; private long transactionCommitTimeout; @@ -121,15 +110,11 @@ public class Shard extends RaftActor { private final ReadyTransactionReply READY_TRANSACTION_REPLY = new ReadyTransactionReply( Serialization.serializedActorPath(getSelf())); + private final DOMTransactionFactory domTransactionFactory; - /** - * Coordinates persistence recovery on startup. - */ - private ShardRecoveryCoordinator recoveryCoordinator; + private final ShardTransactionActorFactory transactionActorFactory; - private final DOMTransactionFactory transactionFactory; - - private final String txnDispatcherPath; + private final ShardSnapshotCohort snapshotCohort; private final DataTreeChangeListenerSupport treeChangeSupport = new DataTreeChangeListenerSupport(this); private final DataChangeListenerSupport changeSupport = new DataChangeListenerSupport(this); @@ -140,9 +125,6 @@ public class Shard extends RaftActor { this.name = name.toString(); this.datastoreContext = datastoreContext; - this.schemaContext = schemaContext; - this.txnDispatcherPath = new Dispatchers(context().system().dispatchers()) - .getDispatcherPath(Dispatchers.DispatcherType.Transaction); setPersistence(datastoreContext.isPersistent()); @@ -164,9 +146,9 @@ public class Shard extends RaftActor { getContext().become(new MeteringBehavior(this)); } - transactionFactory = new DOMTransactionFactory(store, shardMBean, LOG, this.name); + domTransactionFactory = new DOMTransactionFactory(store, shardMBean, LOG, this.name); - commitCoordinator = new ShardCommitCoordinator(transactionFactory, + commitCoordinator = new ShardCommitCoordinator(domTransactionFactory, TimeUnit.SECONDS.convert(5, TimeUnit.MINUTES), datastoreContext.getShardTransactionCommitQueueCapacity(), self(), LOG, this.name); @@ -178,7 +160,11 @@ public class Shard extends RaftActor { appendEntriesReplyTracker = new MessageTracker(AppendEntriesReply.class, getRaftActorContext().getConfigParams().getIsolatedCheckIntervalInMillis()); - recoveryCoordinator = new ShardRecoveryCoordinator(store, persistenceId(), LOG); + transactionActorFactory = new ShardTransactionActorFactory(domTransactionFactory, datastoreContext, + new Dispatchers(context().system().dispatchers()).getDispatcherPath( + Dispatchers.DispatcherType.Transaction), self(), getContext(), shardMBean); + + snapshotCohort = new ShardSnapshotCohort(transactionActorFactory, store, LOG, this.name); } private void setTransactionCommitTimeout() { @@ -447,8 +433,12 @@ public class Shard extends RaftActor { // if(isLeader()) { try { - BatchedModificationsReply reply = commitCoordinator.handleTransactionModifications(batched); - sender().tell(reply, self()); + boolean ready = commitCoordinator.handleTransactionModifications(batched); + if(ready) { + sender().tell(READY_TRANSACTION_REPLY, self()); + } else { + sender().tell(new BatchedModificationsReply(batched.getModifications().size()), self()); + } } catch (Exception e) { LOG.error("{}: Error handling BatchedModifications for Tx {}", persistenceId(), batched.getTransactionID(), e); @@ -488,20 +478,21 @@ public class Shard extends RaftActor { // node. In that case, the subsequent 3-phase commit messages won't contain the // transactionId so to maintain backwards compatibility, we create a separate cohort actor // to provide the compatible behavior. - if(ready.getTxnClientVersion() < DataStoreVersions.HELIUM_1_VERSION) { - LOG.debug("{}: Creating BackwardsCompatibleThreePhaseCommitCohort", persistenceId()); - ActorRef replyActorPath = getContext().actorOf(BackwardsCompatibleThreePhaseCommitCohort.props( - ready.getTransactionID())); + if(ready.getTxnClientVersion() < DataStoreVersions.LITHIUM_VERSION) { + ActorRef replyActorPath = getSelf(); + if(ready.getTxnClientVersion() < DataStoreVersions.HELIUM_1_VERSION) { + LOG.debug("{}: Creating BackwardsCompatibleThreePhaseCommitCohort", persistenceId()); + replyActorPath = getContext().actorOf(BackwardsCompatibleThreePhaseCommitCohort.props( + ready.getTransactionID())); + } ReadyTransactionReply readyTransactionReply = - new ReadyTransactionReply(Serialization.serializedActorPath(replyActorPath)); + new ReadyTransactionReply(Serialization.serializedActorPath(replyActorPath), + ready.getTxnClientVersion()); getSender().tell(ready.isReturnSerialized() ? readyTransactionReply.toSerializable() : - readyTransactionReply, getSelf()); - + readyTransactionReply, getSelf()); } else { - - getSender().tell(ready.isReturnSerialized() ? READY_TRANSACTION_REPLY.toSerializable() : - READY_TRANSACTION_REPLY, getSelf()); + getSender().tell(READY_TRANSACTION_REPLY, getSelf()); } } @@ -558,29 +549,15 @@ public class Shard extends RaftActor { } private void closeTransactionChain(final CloseTransactionChain closeTransactionChain) { - transactionFactory.closeTransactionChain(closeTransactionChain.getTransactionChainId()); + domTransactionFactory.closeTransactionChain(closeTransactionChain.getTransactionChainId()); } private ActorRef createTypedTransactionActor(int transactionType, ShardTransactionIdentifier transactionId, String transactionChainId, short clientVersion ) { - DOMStoreTransaction transaction = transactionFactory.newTransaction( - TransactionProxy.TransactionType.fromInt(transactionType), transactionId.toString(), - transactionChainId); - - return createShardTransaction(transaction, transactionId, clientVersion); - } - - private ActorRef createShardTransaction(DOMStoreTransaction transaction, ShardTransactionIdentifier transactionId, - short clientVersion){ - return getContext().actorOf( - ShardTransaction.props(transaction, getSelf(), - schemaContext, datastoreContext, shardMBean, - transactionId.getRemoteTransactionId(), clientVersion) - .withDispatcher(txnDispatcherPath), - transactionId.toString()); - + return transactionActorFactory.newShardTransaction(TransactionProxy.TransactionType.fromInt(transactionType), + transactionId, transactionChainId, clientVersion); } private void createTransaction(CreateTransaction createTransaction) { @@ -612,18 +589,11 @@ public class Shard extends RaftActor { return transactionActor; } - private void syncCommitTransaction(final DOMStoreWriteTransaction transaction) - throws ExecutionException, InterruptedException { - DOMStoreThreePhaseCommitCohort commitCohort = transaction.ready(); - commitCohort.preCommit().get(); - commitCohort.commit().get(); - } - private void commitWithNewTransaction(final Modification modification) { DOMStoreWriteTransaction tx = store.newWriteOnlyTransaction(); modification.apply(tx); try { - syncCommitTransaction(tx); + snapshotCohort.syncCommitTransaction(tx); shardMBean.incrementCommittedTransactionCount(); shardMBean.setLastCommittedTransactionTime(System.currentTimeMillis()); } catch (InterruptedException | ExecutionException e) { @@ -633,9 +603,7 @@ public class Shard extends RaftActor { } private void updateSchemaContext(final UpdateSchemaContext message) { - this.schemaContext = message.getSchemaContext(); updateSchemaContext(message.getSchemaContext()); - store.onGlobalContextUpdated(message.getSchemaContext()); } @VisibleForTesting @@ -649,30 +617,18 @@ public class Shard extends RaftActor { } @Override - protected - void startLogRecoveryBatch(final int maxBatchSize) { - recoveryCoordinator.startLogRecoveryBatch(maxBatchSize); - } - - @Override - protected void appendRecoveredLogEntry(final Payload data) { - recoveryCoordinator.appendRecoveredLogPayload(data); - } - - @Override - protected void applyRecoverySnapshot(final byte[] snapshotBytes) { - recoveryCoordinator.applyRecoveredSnapshot(snapshotBytes); + protected RaftActorSnapshotCohort getRaftActorSnapshotCohort() { + return snapshotCohort; } @Override - protected void applyCurrentLogRecoveryBatch() { - recoveryCoordinator.applyCurrentLogRecoveryBatch(); + @Nonnull + protected RaftActorRecoveryCohort getRaftActorRecoveryCohort() { + return new ShardRecoveryCoordinator(store, persistenceId(), LOG); } @Override protected void onRecoveryComplete() { - recoveryCoordinator = null; - //notify shard manager getContext().parent().tell(new ActorInitialized(), getSelf()); @@ -727,46 +683,6 @@ public class Shard extends RaftActor { } } - @Override - protected void createSnapshot() { - // Create a transaction actor. We are really going to treat the transaction as a worker - // so that this actor does not get block building the snapshot. THe transaction actor will - // after processing the CreateSnapshot message. - - ActorRef createSnapshotTransaction = createTransaction( - TransactionProxy.TransactionType.READ_ONLY.ordinal(), - "createSnapshot" + ++createSnapshotTransactionCounter, "", - DataStoreVersions.CURRENT_VERSION); - - createSnapshotTransaction.tell(CreateSnapshot.INSTANCE, self()); - } - - @VisibleForTesting - @Override - protected void applySnapshot(final byte[] snapshotBytes) { - // Since this will be done only on Recovery or when this actor is a Follower - // we can safely commit everything in here. We not need to worry about event notifications - // as they would have already been disabled on the follower - - LOG.info("{}: Applying snapshot", persistenceId()); - try { - DOMStoreWriteTransaction transaction = store.newWriteOnlyTransaction(); - - NormalizedNode node = SerializationUtils.deserializeNormalizedNode(snapshotBytes); - - // delete everything first - transaction.delete(DATASTORE_ROOT); - - // Add everything from the remote node back - transaction.write(DATASTORE_ROOT, node); - syncCommitTransaction(transaction); - } catch (InterruptedException | ExecutionException e) { - LOG.error("{}: An exception occurred when applying snapshot", persistenceId(), e); - } finally { - LOG.info("{}: Done applying snapshot", persistenceId()); - } - } - @Override protected void onStateChanged() { boolean isLeader = isLeader(); @@ -781,10 +697,15 @@ public class Shard extends RaftActor { persistenceId(), getId()); } - transactionFactory.closeAllTransactionChains(); + domTransactionFactory.closeAllTransactionChains(); } } + @Override + protected void onLeaderChanged(String oldLeader, String newLeader) { + shardMBean.incrementLeadershipChangeCount(); + } + @Override public String persistenceId() { return this.name; diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardCommitCoordinator.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardCommitCoordinator.java index 54f15fcb4b..b96e38d76a 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardCommitCoordinator.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardCommitCoordinator.java @@ -22,7 +22,6 @@ import java.util.Queue; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.opendaylight.controller.cluster.datastore.messages.BatchedModifications; -import org.opendaylight.controller.cluster.datastore.messages.BatchedModificationsReply; import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction; import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransactionReply; import org.opendaylight.controller.cluster.datastore.modification.Modification; @@ -119,7 +118,7 @@ public class ShardCommitCoordinator { * * @throws ExecutionException if an error occurs loading the cache */ - public BatchedModificationsReply handleTransactionModifications(BatchedModifications batched) + public boolean handleTransactionModifications(BatchedModifications batched) throws ExecutionException { CohortEntry cohortEntry = cohortCache.getIfPresent(batched.getTransactionID()); if(cohortEntry == null) { @@ -137,7 +136,6 @@ public class ShardCommitCoordinator { cohortEntry.applyModifications(batched.getModifications()); - String cohortPath = null; if(batched.isReady()) { if(log.isDebugEnabled()) { log.debug("{}: Readying Tx {}, client version {}", name, @@ -145,10 +143,9 @@ public class ShardCommitCoordinator { } cohortEntry.ready(cohortDecorator); - cohortPath = shardActorPath; } - return new BatchedModificationsReply(batched.getModifications().size(), cohortPath); + return batched.isReady(); } /** diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java index 2e66ef918e..41ca486eb6 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java @@ -26,7 +26,6 @@ import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction; import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** * @author: syedbahm @@ -38,9 +37,8 @@ public class ShardReadTransaction extends ShardTransaction { private final DOMStoreReadTransaction transaction; public ShardReadTransaction(DOMStoreReadTransaction transaction, ActorRef shardActor, - SchemaContext schemaContext, ShardStats shardStats, String transactionID, - short clientTxVersion) { - super(shardActor, schemaContext, shardStats, transactionID, clientTxVersion); + ShardStats shardStats, String transactionID, short clientTxVersion) { + super(shardActor, shardStats, transactionID, clientTxVersion); this.transaction = transaction; } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadWriteTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadWriteTransaction.java index b394da88e8..2042e95577 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadWriteTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadWriteTransaction.java @@ -15,7 +15,6 @@ import org.opendaylight.controller.cluster.datastore.jmx.mbeans.shard.ShardStats import org.opendaylight.controller.cluster.datastore.messages.DataExists; import org.opendaylight.controller.cluster.datastore.messages.ReadData; import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** * @author: syedbahm @@ -25,9 +24,8 @@ public class ShardReadWriteTransaction extends ShardWriteTransaction { private final DOMStoreReadWriteTransaction transaction; public ShardReadWriteTransaction(DOMStoreReadWriteTransaction transaction, ActorRef shardActor, - SchemaContext schemaContext, ShardStats shardStats, String transactionID, - short clientTxVersion) { - super(transaction, shardActor, schemaContext, shardStats, transactionID, clientTxVersion); + ShardStats shardStats, String transactionID, short clientTxVersion) { + super(transaction, shardActor, shardStats, transactionID, clientTxVersion); this.transaction = transaction; } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinator.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinator.java index 7e547d7257..01a124b697 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinator.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinator.java @@ -13,6 +13,7 @@ import java.util.List; import org.opendaylight.controller.cluster.datastore.modification.ModificationPayload; import org.opendaylight.controller.cluster.datastore.modification.MutableCompositeModification; import org.opendaylight.controller.cluster.datastore.utils.SerializationUtils; +import org.opendaylight.controller.cluster.raft.RaftActorRecoveryCohort; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationByteStringPayload; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.CompositeModificationPayload; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; @@ -32,7 +33,7 @@ import org.slf4j.Logger; * * @author Thomas Panetelis */ -class ShardRecoveryCoordinator { +class ShardRecoveryCoordinator implements RaftActorRecoveryCohort { private final InMemoryDOMDataStore store; private List currentLogRecoveryBatch; @@ -45,13 +46,15 @@ class ShardRecoveryCoordinator { this.log = log; } - void startLogRecoveryBatch(int maxBatchSize) { + @Override + public void startLogRecoveryBatch(int maxBatchSize) { currentLogRecoveryBatch = Lists.newArrayListWithCapacity(maxBatchSize); log.debug("{}: starting log recovery batch with max size {}", shardName, maxBatchSize); } - void appendRecoveredLogPayload(Payload payload) { + @Override + public void appendRecoveredLogEntry(Payload payload) { try { if(payload instanceof ModificationPayload) { currentLogRecoveryBatch.add((ModificationPayload) payload); @@ -83,7 +86,8 @@ class ShardRecoveryCoordinator { /** * Applies the current batched log entries to the data store. */ - void applyCurrentLogRecoveryBatch() { + @Override + public void applyCurrentLogRecoveryBatch() { log.debug("{}: Applying current log recovery batch with size {}", shardName, currentLogRecoveryBatch.size()); DOMStoreWriteTransaction writeTx = store.newWriteOnlyTransaction(); @@ -105,7 +109,8 @@ class ShardRecoveryCoordinator { * * @param snapshotBytes the serialized snapshot */ - void applyRecoveredSnapshot(final byte[] snapshotBytes) { + @Override + public void applyRecoverySnapshot(final byte[] snapshotBytes) { log.debug("{}: Applyng recovered sbapshot", shardName); DOMStoreWriteTransaction writeTx = store.newWriteOnlyTransaction(); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardSnapshotCohort.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardSnapshotCohort.java new file mode 100644 index 0000000000..c59085d61c --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardSnapshotCohort.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.datastore; + +import akka.actor.ActorRef; +import java.util.concurrent.ExecutionException; +import org.opendaylight.controller.cluster.datastore.identifiers.ShardTransactionIdentifier; +import org.opendaylight.controller.cluster.datastore.messages.CreateSnapshot; +import org.opendaylight.controller.cluster.datastore.utils.SerializationUtils; +import org.opendaylight.controller.cluster.raft.RaftActorSnapshotCohort; +import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; + +/** + * Participates in raft snapshotting on behalf of a Shard actor. + * + * @author Thomas Pantelis + */ +class ShardSnapshotCohort implements RaftActorSnapshotCohort { + + private static final YangInstanceIdentifier DATASTORE_ROOT = YangInstanceIdentifier.builder().build(); + + private int createSnapshotTransactionCounter; + private final ShardTransactionActorFactory transactionActorFactory; + private final InMemoryDOMDataStore store; + private final Logger log; + private final String logId; + + ShardSnapshotCohort(ShardTransactionActorFactory transactionActorFactory, InMemoryDOMDataStore store, + Logger log, String logId) { + this.transactionActorFactory = transactionActorFactory; + this.store = store; + this.log = log; + this.logId = logId; + } + + @Override + public void createSnapshot(ActorRef actorRef) { + // Create a transaction actor. We are really going to treat the transaction as a worker + // so that this actor does not get block building the snapshot. THe transaction actor will + // after processing the CreateSnapshot message. + + ShardTransactionIdentifier transactionID = new ShardTransactionIdentifier( + "createSnapshot" + ++createSnapshotTransactionCounter); + + ActorRef createSnapshotTransaction = transactionActorFactory.newShardTransaction( + TransactionProxy.TransactionType.READ_ONLY, transactionID, "", DataStoreVersions.CURRENT_VERSION); + + createSnapshotTransaction.tell(CreateSnapshot.INSTANCE, actorRef); + } + + @Override + public void applySnapshot(byte[] snapshotBytes) { + // Since this will be done only on Recovery or when this actor is a Follower + // we can safely commit everything in here. We not need to worry about event notifications + // as they would have already been disabled on the follower + + log.info("{}: Applying snapshot", logId); + + try { + DOMStoreWriteTransaction transaction = store.newWriteOnlyTransaction(); + + NormalizedNode node = SerializationUtils.deserializeNormalizedNode(snapshotBytes); + + // delete everything first + transaction.delete(DATASTORE_ROOT); + + // Add everything from the remote node back + transaction.write(DATASTORE_ROOT, node); + syncCommitTransaction(transaction); + } catch (InterruptedException | ExecutionException e) { + log.error("{}: An exception occurred when applying snapshot", logId, e); + } finally { + log.info("{}: Done applying snapshot", logId); + } + + } + + void syncCommitTransaction(final DOMStoreWriteTransaction transaction) + throws ExecutionException, InterruptedException { + DOMStoreThreePhaseCommitCohort commitCohort = transaction.ready(); + commitCohort.preCommit().get(); + commitCohort.commit().get(); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java index 613b3749e0..066f01b092 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java @@ -31,7 +31,6 @@ import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction; import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** * The ShardTransaction Actor represents a remote transaction @@ -54,25 +53,22 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering protected static final boolean SERIALIZED_REPLY = true; private final ActorRef shardActor; - private final SchemaContext schemaContext; private final ShardStats shardStats; private final String transactionID; private final short clientTxVersion; - protected ShardTransaction(ActorRef shardActor, SchemaContext schemaContext, - ShardStats shardStats, String transactionID, short clientTxVersion) { + protected ShardTransaction(ActorRef shardActor, ShardStats shardStats, String transactionID, + short clientTxVersion) { super("shard-tx"); //actor name override used for metering. This does not change the "real" actor name this.shardActor = shardActor; - this.schemaContext = schemaContext; this.shardStats = shardStats; this.transactionID = transactionID; this.clientTxVersion = clientTxVersion; } public static Props props(DOMStoreTransaction transaction, ActorRef shardActor, - SchemaContext schemaContext,DatastoreContext datastoreContext, ShardStats shardStats, - String transactionID, short txnClientVersion) { - return Props.create(new ShardTransactionCreator(transaction, shardActor, schemaContext, + DatastoreContext datastoreContext, ShardStats shardStats, String transactionID, short txnClientVersion) { + return Props.create(new ShardTransactionCreator(transaction, shardActor, datastoreContext, shardStats, transactionID, txnClientVersion)); } @@ -86,10 +82,6 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering return transactionID; } - protected SchemaContext getSchemaContext() { - return schemaContext; - } - protected short getClientTxVersion() { return clientTxVersion; } @@ -161,19 +153,16 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering final DOMStoreTransaction transaction; final ActorRef shardActor; - final SchemaContext schemaContext; final DatastoreContext datastoreContext; final ShardStats shardStats; final String transactionID; final short txnClientVersion; ShardTransactionCreator(DOMStoreTransaction transaction, ActorRef shardActor, - SchemaContext schemaContext, DatastoreContext datastoreContext, - ShardStats shardStats, String transactionID, short txnClientVersion) { + DatastoreContext datastoreContext, ShardStats shardStats, String transactionID, short txnClientVersion) { this.transaction = transaction; this.shardActor = shardActor; this.shardStats = shardStats; - this.schemaContext = schemaContext; this.datastoreContext = datastoreContext; this.transactionID = transactionID; this.txnClientVersion = txnClientVersion; @@ -184,13 +173,13 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering ShardTransaction tx; if(transaction instanceof DOMStoreReadWriteTransaction) { tx = new ShardReadWriteTransaction((DOMStoreReadWriteTransaction)transaction, - shardActor, schemaContext, shardStats, transactionID, txnClientVersion); + shardActor, shardStats, transactionID, txnClientVersion); } else if(transaction instanceof DOMStoreReadTransaction) { tx = new ShardReadTransaction((DOMStoreReadTransaction)transaction, shardActor, - schemaContext, shardStats, transactionID, txnClientVersion); + shardStats, transactionID, txnClientVersion); } else { tx = new ShardWriteTransaction((DOMStoreWriteTransaction)transaction, - shardActor, schemaContext, shardStats, transactionID, txnClientVersion); + shardActor, shardStats, transactionID, txnClientVersion); } tx.getContext().setReceiveTimeout(datastoreContext.getShardTransactionIdleTimeout()); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransactionChain.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransactionChain.java index 8ba613958a..a4c97e8ab9 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransactionChain.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransactionChain.java @@ -27,14 +27,12 @@ public class ShardTransactionChain extends AbstractUntypedActor { private final DOMStoreTransactionChain chain; private final DatastoreContext datastoreContext; - private final SchemaContext schemaContext; private final ShardStats shardStats; - public ShardTransactionChain(DOMStoreTransactionChain chain, SchemaContext schemaContext, - DatastoreContext datastoreContext, ShardStats shardStats) { + public ShardTransactionChain(DOMStoreTransactionChain chain, DatastoreContext datastoreContext, + ShardStats shardStats) { this.chain = chain; this.datastoreContext = datastoreContext; - this.schemaContext = schemaContext; this.shardStats = shardStats; } @@ -61,22 +59,19 @@ public class ShardTransactionChain extends AbstractUntypedActor { TransactionProxy.TransactionType.READ_ONLY.ordinal()) { return getContext().actorOf( ShardTransaction.props( chain.newReadOnlyTransaction(), getShardActor(), - schemaContext, datastoreContext, shardStats, - createTransaction.getTransactionId(), + datastoreContext, shardStats, createTransaction.getTransactionId(), createTransaction.getVersion()), transactionName); } else if (createTransaction.getTransactionType() == TransactionProxy.TransactionType.READ_WRITE.ordinal()) { return getContext().actorOf( ShardTransaction.props( chain.newReadWriteTransaction(), getShardActor(), - schemaContext, datastoreContext, shardStats, - createTransaction.getTransactionId(), + datastoreContext, shardStats, createTransaction.getTransactionId(), createTransaction.getVersion()), transactionName); } else if (createTransaction.getTransactionType() == TransactionProxy.TransactionType.WRITE_ONLY.ordinal()) { return getContext().actorOf( ShardTransaction.props( chain.newWriteOnlyTransaction(), getShardActor(), - schemaContext, datastoreContext, shardStats, - createTransaction.getTransactionId(), + datastoreContext, shardStats, createTransaction.getTransactionId(), createTransaction.getVersion()), transactionName); } else { throw new IllegalArgumentException ( @@ -94,8 +89,7 @@ public class ShardTransactionChain extends AbstractUntypedActor { public static Props props(DOMStoreTransactionChain chain, SchemaContext schemaContext, DatastoreContext datastoreContext, ShardStats shardStats) { - return Props.create(new ShardTransactionChainCreator(chain, schemaContext, - datastoreContext, shardStats)); + return Props.create(new ShardTransactionChainCreator(chain, datastoreContext, shardStats)); } private static class ShardTransactionChainCreator implements Creator { @@ -103,21 +97,19 @@ public class ShardTransactionChain extends AbstractUntypedActor { final DOMStoreTransactionChain chain; final DatastoreContext datastoreContext; - final SchemaContext schemaContext; final ShardStats shardStats; - ShardTransactionChainCreator(DOMStoreTransactionChain chain, SchemaContext schemaContext, - DatastoreContext datastoreContext, ShardStats shardStats) { + ShardTransactionChainCreator(DOMStoreTransactionChain chain, DatastoreContext datastoreContext, + ShardStats shardStats) { this.chain = chain; this.datastoreContext = datastoreContext; - this.schemaContext = schemaContext; this.shardStats = shardStats; } @Override public ShardTransactionChain create() throws Exception { - return new ShardTransactionChain(chain, schemaContext, datastoreContext, shardStats); + return new ShardTransactionChain(chain, datastoreContext, shardStats); } } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransactionFactory.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransactionFactory.java new file mode 100644 index 0000000000..9637646fc5 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransactionFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.datastore; + +import akka.actor.ActorRef; +import akka.actor.UntypedActorContext; +import org.opendaylight.controller.cluster.datastore.identifiers.ShardTransactionIdentifier; +import org.opendaylight.controller.cluster.datastore.jmx.mbeans.shard.ShardStats; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction; + +/** + * A factory for creating ShardTransaction actors. + * + * @author Thomas Pantelis + */ +class ShardTransactionActorFactory { + + private final DOMTransactionFactory domTransactionFactory; + private final DatastoreContext datastoreContext; + private final String txnDispatcherPath; + private final ShardStats shardMBean; + private final UntypedActorContext actorContext; + private final ActorRef shardActor; + + ShardTransactionActorFactory(DOMTransactionFactory domTransactionFactory, DatastoreContext datastoreContext, + String txnDispatcherPath, ActorRef shardActor, UntypedActorContext actorContext, ShardStats shardMBean) { + this.domTransactionFactory = domTransactionFactory; + this.datastoreContext = datastoreContext; + this.txnDispatcherPath = txnDispatcherPath; + this.shardMBean = shardMBean; + this.actorContext = actorContext; + this.shardActor = shardActor; + } + + ActorRef newShardTransaction(TransactionProxy.TransactionType type, ShardTransactionIdentifier transactionID, + String transactionChainID, short clientVersion) { + + DOMStoreTransaction transaction = domTransactionFactory.newTransaction(type, transactionID.toString(), + transactionChainID); + + return actorContext.actorOf(ShardTransaction.props(transaction, shardActor, datastoreContext, shardMBean, + transactionID.getRemoteTransactionId(), clientVersion).withDispatcher(txnDispatcherPath), + transactionID.toString()); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardWriteTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardWriteTransaction.java index d5dcfde803..1d5b1d8e1b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardWriteTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardWriteTransaction.java @@ -1,6 +1,6 @@ /* - * * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, @@ -32,7 +32,6 @@ import org.opendaylight.controller.cluster.datastore.modification.WriteModificat import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction; import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; /** * @author: syedbahm @@ -41,12 +40,13 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; public class ShardWriteTransaction extends ShardTransaction { private final MutableCompositeModification compositeModification = new MutableCompositeModification(); + private int totalBatchedModificationsReceived; + private Exception lastBatchedModificationsException; private final DOMStoreWriteTransaction transaction; public ShardWriteTransaction(DOMStoreWriteTransaction transaction, ActorRef shardActor, - SchemaContext schemaContext, ShardStats shardStats, String transactionID, - short clientTxVersion) { - super(shardActor, schemaContext, shardStats, transactionID, clientTxVersion); + ShardStats shardStats, String transactionID, short clientTxVersion) { + super(shardActor, shardStats, transactionID, clientTxVersion); this.transaction = transaction; } @@ -88,9 +88,29 @@ public class ShardWriteTransaction extends ShardTransaction { modification.apply(transaction); } - getSender().tell(new BatchedModificationsReply(batched.getModifications().size()), getSelf()); + totalBatchedModificationsReceived++; + if(batched.isReady()) { + if(lastBatchedModificationsException != null) { + throw lastBatchedModificationsException; + } + + if(totalBatchedModificationsReceived != batched.getTotalMessagesSent()) { + throw new IllegalStateException(String.format( + "The total number of batched messages received %d does not match the number sent %d", + totalBatchedModificationsReceived, batched.getTotalMessagesSent())); + } + + readyTransaction(transaction, false); + } else { + getSender().tell(new BatchedModificationsReply(batched.getModifications().size()), getSelf()); + } } catch (Exception e) { + lastBatchedModificationsException = e; getSender().tell(new akka.actor.Status.Failure(e), getSelf()); + + if(batched.isReady()) { + getSelf().tell(PoisonPill.getInstance(), getSelf()); + } } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionContext.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionContext.java index a5a7494e1a..bc6e5f229f 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionContext.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionContext.java @@ -10,7 +10,6 @@ package org.opendaylight.controller.cluster.datastore; import akka.actor.ActorSelection; import com.google.common.base.Optional; import com.google.common.util.concurrent.SettableFuture; -import java.util.Collection; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import scala.concurrent.Future; @@ -33,6 +32,4 @@ interface TransactionContext { void readData(final YangInstanceIdentifier path, SettableFuture>> proxyFuture); void dataExists(YangInstanceIdentifier path, SettableFuture proxyFuture); - - void copyRecordedOperationFutures(Collection> target); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionContextImpl.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionContextImpl.java index c61682d8ef..c722918c5c 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionContextImpl.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionContextImpl.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, @@ -11,18 +12,14 @@ import akka.actor.ActorSelection; import akka.dispatch.Mapper; import akka.dispatch.OnComplete; import com.google.common.base.Optional; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.SettableFuture; -import java.util.List; import org.opendaylight.controller.cluster.datastore.identifiers.TransactionIdentifier; import org.opendaylight.controller.cluster.datastore.messages.BatchedModifications; -import org.opendaylight.controller.cluster.datastore.messages.BatchedModificationsReply; import org.opendaylight.controller.cluster.datastore.messages.CloseTransaction; import org.opendaylight.controller.cluster.datastore.messages.DataExists; import org.opendaylight.controller.cluster.datastore.messages.DataExistsReply; import org.opendaylight.controller.cluster.datastore.messages.ReadData; import org.opendaylight.controller.cluster.datastore.messages.ReadDataReply; -import org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction; import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply; import org.opendaylight.controller.cluster.datastore.messages.SerializableMessage; import org.opendaylight.controller.cluster.datastore.modification.DeleteModification; @@ -49,6 +46,7 @@ public class TransactionContextImpl extends AbstractTransactionContext { private final OperationCompleter operationCompleter; private BatchedModifications batchedModifications; + private int totalBatchedModificationsSent; protected TransactionContextImpl(ActorSelection actor, TransactionIdentifier identifier, String transactionChainId, ActorContext actorContext, SchemaContext schemaContext, boolean isTxActorLocal, @@ -93,90 +91,56 @@ public class TransactionContextImpl extends AbstractTransactionContext { @Override public Future readyTransaction() { - LOG.debug("Tx {} readyTransaction called with {} previous recorded operations pending", - getIdentifier(), recordedOperationCount()); + LOG.debug("Tx {} readyTransaction called", getIdentifier()); - // Send the remaining batched modifications if any. + // Send the remaining batched modifications, if any, with the ready flag set. - sendAndRecordBatchedModifications(); + Future lastModificationsFuture = sendBatchedModifications(true); - // Send the ReadyTransaction message to the Tx actor. - - Future readyReplyFuture = executeOperationAsync(ReadyTransaction.INSTANCE); - - return combineRecordedOperationsFutures(readyReplyFuture); + return transformReadyReply(lastModificationsFuture); } - protected Future combineRecordedOperationsFutures(final Future withLastReplyFuture) { - // Combine all the previously recorded put/merge/delete operation reply Futures and the - // ReadyTransactionReply Future into one Future. If any one fails then the combined - // Future will fail. We need all prior operations and the ready operation to succeed - // in order to attempt commit. - - List> futureList = Lists.newArrayListWithCapacity(recordedOperationCount() + 1); - copyRecordedOperationFutures(futureList); - futureList.add(withLastReplyFuture); - - Future> combinedFutures = akka.dispatch.Futures.sequence(futureList, - actorContext.getClientDispatcher()); - - // Transform the combined Future into a Future that returns the cohort actor path from - // the ReadyTransactionReply. That's the end result of the ready operation. + protected Future transformReadyReply(final Future readyReplyFuture) { + // Transform the last reply Future into a Future that returns the cohort actor path from + // the last reply message. That's the end result of the ready operation. - return combinedFutures.transform(new Mapper, ActorSelection>() { + return readyReplyFuture.transform(new Mapper() { @Override - public ActorSelection checkedApply(Iterable notUsed) { - LOG.debug("Tx {} readyTransaction: pending recorded operations succeeded", - getIdentifier()); - - // At this point all the Futures succeeded and we need to extract the cohort - // actor path from the ReadyTransactionReply. For the recorded operations, they - // don't return any data so we're only interested that they completed - // successfully. We could be paranoid and verify the correct reply types but - // that really should never happen so it's not worth the overhead of - // de-serializing each reply. - - // Note the Future get call here won't block as it's complete. - Object serializedReadyReply = withLastReplyFuture.value().get().get(); - if (serializedReadyReply instanceof ReadyTransactionReply) { - return actorContext.actorSelection(((ReadyTransactionReply)serializedReadyReply).getCohortPath()); - } else if(serializedReadyReply instanceof BatchedModificationsReply) { - return actorContext.actorSelection(((BatchedModificationsReply)serializedReadyReply).getCohortPath()); - } else if(serializedReadyReply.getClass().equals(ReadyTransactionReply.SERIALIZABLE_CLASS)) { - ReadyTransactionReply reply = ReadyTransactionReply.fromSerializable(serializedReadyReply); - String cohortPath = deserializeCohortPath(reply.getCohortPath()); - return actorContext.actorSelection(cohortPath); - } else { - // Throwing an exception here will fail the Future. - throw new IllegalArgumentException(String.format("%s: Invalid reply type %s", - getIdentifier(), serializedReadyReply.getClass())); + public ActorSelection checkedApply(Object serializedReadyReply) { + LOG.debug("Tx {} readyTransaction", getIdentifier()); + + // At this point the ready operation succeeded and we need to extract the cohort + // actor path from the reply. + if(ReadyTransactionReply.isSerializedType(serializedReadyReply)) { + ReadyTransactionReply readyTxReply = ReadyTransactionReply.fromSerializable(serializedReadyReply); + return actorContext.actorSelection(extractCohortPathFrom(readyTxReply)); } + + // Throwing an exception here will fail the Future. + throw new IllegalArgumentException(String.format("%s: Invalid reply type %s", + getIdentifier(), serializedReadyReply.getClass())); } }, TransactionProxy.SAME_FAILURE_TRANSFORMER, actorContext.getClientDispatcher()); } - protected String deserializeCohortPath(String cohortPath) { - return cohortPath; + protected String extractCohortPathFrom(ReadyTransactionReply readyTxReply) { + return readyTxReply.getCohortPath(); + } + + private BatchedModifications newBatchedModifications() { + return new BatchedModifications(getIdentifier().toString(), remoteTransactionVersion, transactionChainId); } private void batchModification(Modification modification) { if(batchedModifications == null) { - batchedModifications = new BatchedModifications(getIdentifier().toString(), remoteTransactionVersion, - transactionChainId); + batchedModifications = newBatchedModifications(); } batchedModifications.addModification(modification); if(batchedModifications.getModifications().size() >= actorContext.getDatastoreContext().getShardBatchedModificationCount()) { - sendAndRecordBatchedModifications(); - } - } - - private void sendAndRecordBatchedModifications() { - Future sentFuture = sendBatchedModifications(); - if(sentFuture != null) { - recordOperationFuture(sentFuture); + sendBatchedModifications(); } } @@ -186,17 +150,25 @@ public class TransactionContextImpl extends AbstractTransactionContext { protected Future sendBatchedModifications(boolean ready) { Future sent = null; - if(batchedModifications != null) { + if(ready || (batchedModifications != null && !batchedModifications.getModifications().isEmpty())) { + if(batchedModifications == null) { + batchedModifications = newBatchedModifications(); + } + if(LOG.isDebugEnabled()) { LOG.debug("Tx {} sending {} batched modifications, ready: {}", getIdentifier(), batchedModifications.getModifications().size(), ready); } batchedModifications.setReady(ready); + batchedModifications.setTotalMessagesSent(++totalBatchedModificationsSent); sent = executeOperationAsync(batchedModifications); - batchedModifications = new BatchedModifications(getIdentifier().toString(), remoteTransactionVersion, - transactionChainId); + if(ready) { + batchedModifications = null; + } else { + batchedModifications = newBatchedModifications(); + } } return sent; @@ -232,7 +204,7 @@ public class TransactionContextImpl extends AbstractTransactionContext { // Send any batched modifications. This is necessary to honor the read uncommitted semantics of the // public API contract. - sendAndRecordBatchedModifications(); + sendBatchedModifications(); OnComplete onComplete = new OnComplete() { @Override @@ -274,7 +246,7 @@ public class TransactionContextImpl extends AbstractTransactionContext { // Send any batched modifications. This is necessary to honor the read uncommitted semantics of the // public API contract. - sendAndRecordBatchedModifications(); + sendBatchedModifications(); OnComplete onComplete = new OnComplete() { @Override diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionFutureCallback.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionFutureCallback.java new file mode 100644 index 0000000000..a8a93c5e7c --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionFutureCallback.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.datastore; + +import akka.actor.ActorSelection; +import akka.dispatch.OnComplete; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import javax.annotation.concurrent.GuardedBy; +import org.opendaylight.controller.cluster.datastore.TransactionProxy.TransactionType; +import org.opendaylight.controller.cluster.datastore.exceptions.NoShardLeaderException; +import org.opendaylight.controller.cluster.datastore.identifiers.TransactionIdentifier; +import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction; +import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply; +import org.opendaylight.controller.cluster.datastore.utils.ActorContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.concurrent.Future; +import scala.concurrent.duration.FiniteDuration; + +/** + * Implements a Future OnComplete callback for a CreateTransaction message. This class handles + * retries, up to a limit, if the shard doesn't have a leader yet. This is done by scheduling a + * retry task after a short delay. + *

+ * The end result from a completed CreateTransaction message is a TransactionContext that is + * used to perform transaction operations. Transaction operations that occur before the + * CreateTransaction completes are cache and executed once the CreateTransaction completes, + * successfully or not. + */ +final class TransactionFutureCallback extends OnComplete { + private static final Logger LOG = LoggerFactory.getLogger(TransactionFutureCallback.class); + + /** + * Time interval in between transaction create retries. + */ + private static final FiniteDuration CREATE_TX_TRY_INTERVAL = FiniteDuration.create(1, TimeUnit.SECONDS); + + /** + * The list of transaction operations to execute once the CreateTransaction completes. + */ + @GuardedBy("txOperationsOnComplete") + private final List txOperationsOnComplete = Lists.newArrayList(); + private final TransactionProxy proxy; + private final String shardName; + + /** + * The TransactionContext resulting from the CreateTransaction reply. + */ + private volatile TransactionContext transactionContext; + + /** + * The target primary shard. + */ + private volatile ActorSelection primaryShard; + private volatile int createTxTries; + + TransactionFutureCallback(final TransactionProxy proxy, final String shardName) { + this.proxy = Preconditions.checkNotNull(proxy); + this.shardName = shardName; + createTxTries = (int) (proxy.getActorContext().getDatastoreContext(). + getShardLeaderElectionTimeout().duration().toMillis() / + CREATE_TX_TRY_INTERVAL.toMillis()); + } + + String getShardName() { + return shardName; + } + + TransactionContext getTransactionContext() { + return transactionContext; + } + + private TransactionType getTransactionType() { + return proxy.getTransactionType(); + } + + private TransactionIdentifier getIdentifier() { + return proxy.getIdentifier(); + } + + private ActorContext getActorContext() { + return proxy.getActorContext(); + } + + private Semaphore getOperationLimiter() { + return proxy.getOperationLimiter(); + } + + /** + * Sets the target primary shard and initiates a CreateTransaction try. + */ + void setPrimaryShard(ActorSelection primaryShard) { + this.primaryShard = primaryShard; + + if (getTransactionType() == TransactionType.WRITE_ONLY && + getActorContext().getDatastoreContext().isWriteOnlyTransactionOptimizationsEnabled()) { + LOG.debug("Tx {} Primary shard {} found - creating WRITE_ONLY transaction context", + getIdentifier(), primaryShard); + + // For write-only Tx's we prepare the transaction modifications directly on the shard actor + // to avoid the overhead of creating a separate transaction actor. + // FIXME: can't assume the shard version is LITHIUM_VERSION - need to obtain it somehow. + executeTxOperatonsOnComplete(proxy.createValidTransactionContext(this.primaryShard, + this.primaryShard.path().toString(), DataStoreVersions.LITHIUM_VERSION)); + } else { + tryCreateTransaction(); + } + } + + /** + * Adds a TransactionOperation to be executed after the CreateTransaction completes. + */ + private void addTxOperationOnComplete(TransactionOperation operation) { + boolean invokeOperation = true; + synchronized(txOperationsOnComplete) { + if(transactionContext == null) { + LOG.debug("Tx {} Adding operation on complete", getIdentifier()); + + invokeOperation = false; + txOperationsOnComplete.add(operation); + } + } + + if(invokeOperation) { + operation.invoke(transactionContext); + } + } + + void enqueueTransactionOperation(final TransactionOperation op) { + + if (transactionContext != null) { + op.invoke(transactionContext); + } else { + // The shard Tx hasn't been created yet so add the Tx operation to the Tx Future + // callback to be executed after the Tx is created. + addTxOperationOnComplete(op); + } + } + + /** + * Performs a CreateTransaction try async. + */ + private void tryCreateTransaction() { + if(LOG.isDebugEnabled()) { + LOG.debug("Tx {} Primary shard {} found - trying create transaction", getIdentifier(), primaryShard); + } + + Object serializedCreateMessage = new CreateTransaction(getIdentifier().toString(), + getTransactionType().ordinal(), proxy.getTransactionChainId()).toSerializable(); + + Future createTxFuture = getActorContext().executeOperationAsync(primaryShard, serializedCreateMessage); + + createTxFuture.onComplete(this, getActorContext().getClientDispatcher()); + } + + @Override + public void onComplete(Throwable failure, Object response) { + if(failure instanceof NoShardLeaderException) { + // There's no leader for the shard yet - schedule and try again, unless we're out + // of retries. Note: createTxTries is volatile as it may be written by different + // threads however not concurrently, therefore decrementing it non-atomically here + // is ok. + if(--createTxTries > 0) { + LOG.debug("Tx {} Shard {} has no leader yet - scheduling create Tx retry", + getIdentifier(), shardName); + + getActorContext().getActorSystem().scheduler().scheduleOnce(CREATE_TX_TRY_INTERVAL, + new Runnable() { + @Override + public void run() { + tryCreateTransaction(); + } + }, getActorContext().getClientDispatcher()); + return; + } + } + + createTransactionContext(failure, response); + } + + void createTransactionContext(Throwable failure, Object response) { + // Mainly checking for state violation here to perform a volatile read of "initialized" to + // ensure updates to operationLimter et al are visible to this thread (ie we're doing + // "piggy-back" synchronization here). + proxy.ensureInitializied(); + + // Create the TransactionContext from the response or failure. Store the new + // TransactionContext locally until we've completed invoking the + // TransactionOperations. This avoids thread timing issues which could cause + // out-of-order TransactionOperations. Eg, on a modification operation, if the + // TransactionContext is non-null, then we directly call the TransactionContext. + // However, at the same time, the code may be executing the cached + // TransactionOperations. So to avoid thus timing, we don't publish the + // TransactionContext until after we've executed all cached TransactionOperations. + TransactionContext localTransactionContext; + if(failure != null) { + LOG.debug("Tx {} Creating NoOpTransaction because of error", getIdentifier(), failure); + + localTransactionContext = new NoOpTransactionContext(failure, getIdentifier(), getOperationLimiter()); + } else if (CreateTransactionReply.SERIALIZABLE_CLASS.equals(response.getClass())) { + localTransactionContext = createValidTransactionContext( + CreateTransactionReply.fromSerializable(response)); + } else { + IllegalArgumentException exception = new IllegalArgumentException(String.format( + "Invalid reply type %s for CreateTransaction", response.getClass())); + + localTransactionContext = new NoOpTransactionContext(exception, getIdentifier(), getOperationLimiter()); + } + + executeTxOperatonsOnComplete(localTransactionContext); + } + + private void executeTxOperatonsOnComplete(TransactionContext localTransactionContext) { + while(true) { + // Access to txOperationsOnComplete and transactionContext must be protected and atomic + // (ie synchronized) with respect to #addTxOperationOnComplete to handle timing + // issues and ensure no TransactionOperation is missed and that they are processed + // in the order they occurred. + + // We'll make a local copy of the txOperationsOnComplete list to handle re-entrancy + // in case a TransactionOperation results in another transaction operation being + // queued (eg a put operation from a client read Future callback that is notified + // synchronously). + Collection operationsBatch = null; + synchronized(txOperationsOnComplete) { + if(txOperationsOnComplete.isEmpty()) { + // We're done invoking the TransactionOperations so we can now publish the + // TransactionContext. + transactionContext = localTransactionContext; + break; + } + + operationsBatch = new ArrayList<>(txOperationsOnComplete); + txOperationsOnComplete.clear(); + } + + // Invoke TransactionOperations outside the sync block to avoid unnecessary blocking. + // A slight down-side is that we need to re-acquire the lock below but this should + // be negligible. + for(TransactionOperation oper: operationsBatch) { + oper.invoke(localTransactionContext); + } + } + } + + private TransactionContext createValidTransactionContext(CreateTransactionReply reply) { + LOG.debug("Tx {} Received {}", getIdentifier(), reply); + + return proxy.createValidTransactionContext(getActorContext().actorSelection(reply.getTransactionPath()), + reply.getTransactionPath(), reply.getVersion()); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionProxy.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionProxy.java index 59c9298499..71799c92d4 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionProxy.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionProxy.java @@ -16,36 +16,36 @@ import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import javax.annotation.concurrent.GuardedBy; import org.opendaylight.controller.cluster.datastore.compat.PreLithiumTransactionContextImpl; -import org.opendaylight.controller.cluster.datastore.exceptions.NoShardLeaderException; import org.opendaylight.controller.cluster.datastore.identifiers.TransactionIdentifier; -import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction; -import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply; import org.opendaylight.controller.cluster.datastore.shardstrategy.ShardStrategyFactory; import org.opendaylight.controller.cluster.datastore.utils.ActorContext; +import org.opendaylight.controller.cluster.datastore.utils.NormalizedNodeAggregator; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.sal.core.spi.data.AbstractDOMStoreTransaction; import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction; import org.opendaylight.yangtools.util.concurrent.MappingCheckedFuture; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scala.concurrent.Future; import scala.concurrent.Promise; -import scala.concurrent.duration.FiniteDuration; /** * TransactionProxy acts as a proxy for one or more transactions that were created on a remote shard @@ -96,12 +96,6 @@ public class TransactionProxy extends AbstractDOMStoreTransaction> getRecordedOperationFutures() { - List> recordedOperationFutures = Lists.newArrayList(); - for(TransactionFutureCallback txFutureCallback : txFutureCallbackMap.values()) { - TransactionContext transactionContext = txFutureCallback.getTransactionContext(); - if (transactionContext != null) { - transactionContext.copyRecordedOperationFutures(recordedOperationFutures); - } - } - - return recordedOperationFutures; - } - @VisibleForTesting boolean hasTransactionContext() { for(TransactionFutureCallback txFutureCallback : txFutureCallbackMap.values()) { @@ -178,6 +159,10 @@ public class TransactionProxy extends AbstractDOMStoreTransaction>, ReadFailedException> read(final YangInstanceIdentifier path) { @@ -186,21 +171,62 @@ public class TransactionProxy extends AbstractDOMStoreTransaction>> proxyFuture = SettableFuture.create(); - TransactionFutureCallback txFutureCallback = getOrCreateTxFutureCallback(path); - txFutureCallback.enqueueTransactionOperation(new TransactionOperation() { - @Override - public void invoke(TransactionContext transactionContext) { - transactionContext.readData(path, proxyFuture); - } - }); + if(isRootPath(path)){ + readAllData(path, proxyFuture); + } else { + throttleOperation(); + + TransactionFutureCallback txFutureCallback = getOrCreateTxFutureCallback(path); + txFutureCallback.enqueueTransactionOperation(new TransactionOperation() { + @Override + public void invoke(TransactionContext transactionContext) { + transactionContext.readData(path, proxyFuture); + } + }); + + } return MappingCheckedFuture.create(proxyFuture, ReadFailedException.MAPPER); } + private void readAllData(final YangInstanceIdentifier path, + final SettableFuture>> proxyFuture) { + Set allShardNames = actorContext.getConfiguration().getAllShardNames(); + List>>> futures = new ArrayList<>(allShardNames.size()); + + for(String shardName : allShardNames){ + final SettableFuture>> subProxyFuture = SettableFuture.create(); + + throttleOperation(); + + TransactionFutureCallback txFutureCallback = getOrCreateTxFutureCallback(shardName); + txFutureCallback.enqueueTransactionOperation(new TransactionOperation() { + @Override + public void invoke(TransactionContext transactionContext) { + transactionContext.readData(path, subProxyFuture); + } + }); + + futures.add(subProxyFuture); + } + + final ListenableFuture>>> future = Futures.allAsList(futures); + + future.addListener(new Runnable() { + @Override + public void run() { + try { + proxyFuture.set(NormalizedNodeAggregator.aggregate(YangInstanceIdentifier.builder().build(), + future.get(), actorContext.getSchemaContext())); + } catch (DataValidationFailedException | InterruptedException | ExecutionException e) { + proxyFuture.setException(e); + } + } + }, actorContext.getActorSystem().dispatcher()); + } + @Override public CheckedFuture exists(final YangInstanceIdentifier path) { @@ -260,6 +286,10 @@ public class TransactionProxy extends AbstractDOMStoreTransaction data) { @@ -407,13 +437,25 @@ public class TransactionProxy extends AbstractDOMStoreTransaction findPrimaryFuture = sendFindPrimaryShardAsync(shardName); - final TransactionFutureCallback newTxFutureCallback = new TransactionFutureCallback(shardName); + final TransactionFutureCallback newTxFutureCallback = new TransactionFutureCallback(this, shardName); txFutureCallback = newTxFutureCallback; txFutureCallbackMap.put(shardName, txFutureCallback); @@ -433,7 +475,7 @@ public class TransactionProxy extends AbstractDOMStoreTransaction - * The end result from a completed CreateTransaction message is a TransactionContext that is - * used to perform transaction operations. Transaction operations that occur before the - * CreateTransaction completes are cache and executed once the CreateTransaction completes, - * successfully or not. - */ - private class TransactionFutureCallback extends OnComplete { - - /** - * The list of transaction operations to execute once the CreateTransaction completes. - */ - @GuardedBy("txOperationsOnComplete") - private final List txOperationsOnComplete = Lists.newArrayList(); - - /** - * The TransactionContext resulting from the CreateTransaction reply. - */ - private volatile TransactionContext transactionContext; - - /** - * The target primary shard. - */ - private volatile ActorSelection primaryShard; + TransactionContext createValidTransactionContext(ActorSelection transactionActor, + String transactionPath, short remoteTransactionVersion) { - private volatile int createTxTries = (int) (actorContext.getDatastoreContext(). - getShardLeaderElectionTimeout().duration().toMillis() / - CREATE_TX_TRY_INTERVAL.toMillis()); + if (transactionType == TransactionType.READ_ONLY) { + // Read-only Tx's aren't explicitly closed by the client so we create a PhantomReference + // to close the remote Tx's when this instance is no longer in use and is garbage + // collected. - private final String shardName; + if(remoteTransactionActorsMB == null) { + remoteTransactionActors = Lists.newArrayList(); + remoteTransactionActorsMB = new AtomicBoolean(); - TransactionFutureCallback(String shardName) { - this.shardName = shardName; - } - - String getShardName() { - return shardName; - } - - TransactionContext getTransactionContext() { - return transactionContext; - } - - - /** - * Sets the target primary shard and initiates a CreateTransaction try. - */ - void setPrimaryShard(ActorSelection primaryShard) { - this.primaryShard = primaryShard; - - if(transactionType == TransactionType.WRITE_ONLY && - actorContext.getDatastoreContext().isWriteOnlyTransactionOptimizationsEnabled()) { - LOG.debug("Tx {} Primary shard {} found - creating WRITE_ONLY transaction context", - getIdentifier(), primaryShard); - - // For write-only Tx's we prepare the transaction modifications directly on the shard actor - // to avoid the overhead of creating a separate transaction actor. - // FIXME: can't assume the shard version is LITHIUM_VERSION - need to obtain it somehow. - executeTxOperatonsOnComplete(createValidTransactionContext(this.primaryShard, - this.primaryShard.path().toString(), DataStoreVersions.LITHIUM_VERSION)); - } else { - tryCreateTransaction(); - } - } - - /** - * Adds a TransactionOperation to be executed after the CreateTransaction completes. - */ - void addTxOperationOnComplete(TransactionOperation operation) { - boolean invokeOperation = true; - synchronized(txOperationsOnComplete) { - if(transactionContext == null) { - LOG.debug("Tx {} Adding operation on complete", getIdentifier()); - - invokeOperation = false; - txOperationsOnComplete.add(operation); - } + TransactionProxyCleanupPhantomReference.track(TransactionProxy.this); } - if(invokeOperation) { - operation.invoke(transactionContext); - } - } + // Add the actor to the remoteTransactionActors list for access by the + // cleanup PhantonReference. + remoteTransactionActors.add(transactionActor); - void enqueueTransactionOperation(final TransactionOperation op) { - - if (transactionContext != null) { - op.invoke(transactionContext); - } else { - // The shard Tx hasn't been created yet so add the Tx operation to the Tx Future - // callback to be executed after the Tx is created. - addTxOperationOnComplete(op); - } - } - - /** - * Performs a CreateTransaction try async. - */ - private void tryCreateTransaction() { - if(LOG.isDebugEnabled()) { - LOG.debug("Tx {} Primary shard {} found - trying create transaction", getIdentifier(), primaryShard); - } - - Object serializedCreateMessage = new CreateTransaction(getIdentifier().toString(), - TransactionProxy.this.transactionType.ordinal(), - getTransactionChainId()).toSerializable(); - - Future createTxFuture = actorContext.executeOperationAsync(primaryShard, serializedCreateMessage); - - createTxFuture.onComplete(this, actorContext.getClientDispatcher()); - } - - @Override - public void onComplete(Throwable failure, Object response) { - if(failure instanceof NoShardLeaderException) { - // There's no leader for the shard yet - schedule and try again, unless we're out - // of retries. Note: createTxTries is volatile as it may be written by different - // threads however not concurrently, therefore decrementing it non-atomically here - // is ok. - if(--createTxTries > 0) { - LOG.debug("Tx {} Shard {} has no leader yet - scheduling create Tx retry", - getIdentifier(), shardName); - - actorContext.getActorSystem().scheduler().scheduleOnce(CREATE_TX_TRY_INTERVAL, - new Runnable() { - @Override - public void run() { - tryCreateTransaction(); - } - }, actorContext.getClientDispatcher()); - return; - } - } - - createTransactionContext(failure, response); - } - - private void createTransactionContext(Throwable failure, Object response) { - // Mainly checking for state violation here to perform a volatile read of "initialized" to - // ensure updates to operationLimter et al are visible to this thread (ie we're doing - // "piggy-back" synchronization here). - Preconditions.checkState(initialized, "Tx was not propertly initialized."); - - // Create the TransactionContext from the response or failure. Store the new - // TransactionContext locally until we've completed invoking the - // TransactionOperations. This avoids thread timing issues which could cause - // out-of-order TransactionOperations. Eg, on a modification operation, if the - // TransactionContext is non-null, then we directly call the TransactionContext. - // However, at the same time, the code may be executing the cached - // TransactionOperations. So to avoid thus timing, we don't publish the - // TransactionContext until after we've executed all cached TransactionOperations. - TransactionContext localTransactionContext; - if(failure != null) { - LOG.debug("Tx {} Creating NoOpTransaction because of error", getIdentifier(), failure); - - localTransactionContext = new NoOpTransactionContext(failure, getIdentifier(), operationLimiter); - } else if (CreateTransactionReply.SERIALIZABLE_CLASS.equals(response.getClass())) { - localTransactionContext = createValidTransactionContext( - CreateTransactionReply.fromSerializable(response)); - } else { - IllegalArgumentException exception = new IllegalArgumentException(String.format( - "Invalid reply type %s for CreateTransaction", response.getClass())); - - localTransactionContext = new NoOpTransactionContext(exception, getIdentifier(), operationLimiter); - } - - executeTxOperatonsOnComplete(localTransactionContext); - } - - private void executeTxOperatonsOnComplete(TransactionContext localTransactionContext) { - while(true) { - // Access to txOperationsOnComplete and transactionContext must be protected and atomic - // (ie synchronized) with respect to #addTxOperationOnComplete to handle timing - // issues and ensure no TransactionOperation is missed and that they are processed - // in the order they occurred. - - // We'll make a local copy of the txOperationsOnComplete list to handle re-entrancy - // in case a TransactionOperation results in another transaction operation being - // queued (eg a put operation from a client read Future callback that is notified - // synchronously). - Collection operationsBatch = null; - synchronized(txOperationsOnComplete) { - if(txOperationsOnComplete.isEmpty()) { - // We're done invoking the TransactionOperations so we can now publish the - // TransactionContext. - transactionContext = localTransactionContext; - break; - } - - operationsBatch = new ArrayList<>(txOperationsOnComplete); - txOperationsOnComplete.clear(); - } - - // Invoke TransactionOperations outside the sync block to avoid unnecessary blocking. - // A slight down-side is that we need to re-acquire the lock below but this should - // be negligible. - for(TransactionOperation oper: operationsBatch) { - oper.invoke(localTransactionContext); - } - } - } - - private TransactionContext createValidTransactionContext(CreateTransactionReply reply) { - LOG.debug("Tx {} Received {}", getIdentifier(), reply); - - return createValidTransactionContext(actorContext.actorSelection(reply.getTransactionPath()), - reply.getTransactionPath(), reply.getVersion()); + // Write to the memory barrier volatile to publish the above update to the + // remoteTransactionActors list for thread visibility. + remoteTransactionActorsMB.set(true); } - private TransactionContext createValidTransactionContext(ActorSelection transactionActor, - String transactionPath, short remoteTransactionVersion) { - - if (transactionType == TransactionType.READ_ONLY) { - // Read-only Tx's aren't explicitly closed by the client so we create a PhantomReference - // to close the remote Tx's when this instance is no longer in use and is garbage - // collected. + // TxActor is always created where the leader of the shard is. + // Check if TxActor is created in the same node + boolean isTxActorLocal = actorContext.isPathLocal(transactionPath); - if(remoteTransactionActorsMB == null) { - remoteTransactionActors = Lists.newArrayList(); - remoteTransactionActorsMB = new AtomicBoolean(); - - TransactionProxyCleanupPhantomReference.track(TransactionProxy.this); - } - - // Add the actor to the remoteTransactionActors list for access by the - // cleanup PhantonReference. - remoteTransactionActors.add(transactionActor); - - // Write to the memory barrier volatile to publish the above update to the - // remoteTransactionActors list for thread visibility. - remoteTransactionActorsMB.set(true); - } - - // TxActor is always created where the leader of the shard is. - // Check if TxActor is created in the same node - boolean isTxActorLocal = actorContext.isPathLocal(transactionPath); - - if(remoteTransactionVersion < DataStoreVersions.LITHIUM_VERSION) { - return new PreLithiumTransactionContextImpl(transactionPath, transactionActor, getIdentifier(), - transactionChainId, actorContext, schemaContext, isTxActorLocal, remoteTransactionVersion, - operationCompleter); - } else if (transactionType == TransactionType.WRITE_ONLY && - actorContext.getDatastoreContext().isWriteOnlyTransactionOptimizationsEnabled()) { - return new WriteOnlyTransactionContextImpl(transactionActor, getIdentifier(), transactionChainId, + if(remoteTransactionVersion < DataStoreVersions.LITHIUM_VERSION) { + return new PreLithiumTransactionContextImpl(transactionPath, transactionActor, getIdentifier(), + transactionChainId, actorContext, schemaContext, isTxActorLocal, remoteTransactionVersion, + operationCompleter); + } else { + return new TransactionContextImpl(transactionActor, getIdentifier(), transactionChainId, actorContext, schemaContext, isTxActorLocal, remoteTransactionVersion, operationCompleter); - } else { - return new TransactionContextImpl(transactionActor, getIdentifier(), transactionChainId, - actorContext, schemaContext, isTxActorLocal, remoteTransactionVersion, operationCompleter); - } } } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/WriteOnlyTransactionContextImpl.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/WriteOnlyTransactionContextImpl.java deleted file mode 100644 index e1313540c4..0000000000 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/WriteOnlyTransactionContextImpl.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.cluster.datastore; - -import akka.actor.ActorSelection; -import org.opendaylight.controller.cluster.datastore.identifiers.TransactionIdentifier; -import org.opendaylight.controller.cluster.datastore.utils.ActorContext; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import scala.concurrent.Future; - -/** - * Context for a write-only transaction. - * - * @author Thomas Pantelis - */ -public class WriteOnlyTransactionContextImpl extends TransactionContextImpl { - private static final Logger LOG = LoggerFactory.getLogger(WriteOnlyTransactionContextImpl.class); - - public WriteOnlyTransactionContextImpl(ActorSelection actor, TransactionIdentifier identifier, - String transactionChainId, ActorContext actorContext, SchemaContext schemaContext, boolean isTxActorLocal, - short remoteTransactionVersion, OperationCompleter operationCompleter) { - super(actor, identifier, transactionChainId, actorContext, schemaContext, isTxActorLocal, - remoteTransactionVersion, operationCompleter); - } - - @Override - public Future readyTransaction() { - LOG.debug("Tx {} readyTransaction called with {} previous recorded operations pending", - getIdentifier(), recordedOperationCount()); - - // Send the remaining batched modifications if any. - - Future lastModificationsFuture = sendBatchedModifications(true); - - return combineRecordedOperationsFutures(lastModificationsFuture); - } -} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumTransactionContextImpl.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumTransactionContextImpl.java index c3450333a4..d17497c18c 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumTransactionContextImpl.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumTransactionContextImpl.java @@ -15,6 +15,7 @@ import org.opendaylight.controller.cluster.datastore.identifiers.TransactionIden import org.opendaylight.controller.cluster.datastore.messages.DeleteData; import org.opendaylight.controller.cluster.datastore.messages.MergeData; import org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction; +import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply; import org.opendaylight.controller.cluster.datastore.messages.WriteData; import org.opendaylight.controller.cluster.datastore.utils.ActorContext; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; @@ -45,36 +46,32 @@ public class PreLithiumTransactionContextImpl extends TransactionContextImpl { @Override public void deleteData(YangInstanceIdentifier path) { - recordOperationFuture(executeOperationAsync( - new DeleteData(path, getRemoteTransactionVersion()))); + executeOperationAsync(new DeleteData(path, getRemoteTransactionVersion())); } @Override public void mergeData(YangInstanceIdentifier path, NormalizedNode data) { - recordOperationFuture(executeOperationAsync( - new MergeData(path, data, getRemoteTransactionVersion()))); + executeOperationAsync(new MergeData(path, data, getRemoteTransactionVersion())); } @Override public void writeData(YangInstanceIdentifier path, NormalizedNode data) { - recordOperationFuture(executeOperationAsync( - new WriteData(path, data, getRemoteTransactionVersion()))); + executeOperationAsync(new WriteData(path, data, getRemoteTransactionVersion())); } @Override public Future readyTransaction() { - LOG.debug("Tx {} readyTransaction called with {} previous recorded operations pending", - getIdentifier(), recordedOperationCount()); + LOG.debug("Tx {} readyTransaction called", getIdentifier()); // Send the ReadyTransaction message to the Tx actor. Future lastReplyFuture = executeOperationAsync(ReadyTransaction.INSTANCE); - return combineRecordedOperationsFutures(lastReplyFuture); + return transformReadyReply(lastReplyFuture); } @Override - protected String deserializeCohortPath(String cohortPath) { + protected String extractCohortPathFrom(ReadyTransactionReply readyTxReply) { // In base Helium we used to return the local path of the actor which represented // a remote ThreePhaseCommitCohort. The local path would then be converted to // a remote path using this resolvePath method. To maintain compatibility with @@ -83,9 +80,9 @@ public class PreLithiumTransactionContextImpl extends TransactionContextImpl { // we could remove this code to resolvePath and just use the cohortPath as the // resolved cohortPath if(getRemoteTransactionVersion() < DataStoreVersions.HELIUM_1_VERSION) { - return getActorContext().resolvePath(transactionPath, cohortPath); + return getActorContext().resolvePath(transactionPath, readyTxReply.getCohortPath()); } - return cohortPath; + return readyTxReply.getCohortPath(); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/jmx/mbeans/shard/ShardStats.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/jmx/mbeans/shard/ShardStats.java index fb59b7643f..e3c8ced878 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/jmx/mbeans/shard/ShardStats.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/jmx/mbeans/shard/ShardStats.java @@ -77,6 +77,10 @@ public class ShardStats extends AbstractMXBean implements ShardStatsMXBean { private String statRetrievalTime; + private long leadershipChangeCount; + + private long lastLeadershipChangeTime; + public ShardStats(final String shardName, final String mxBeanType) { super(shardName, mxBeanType, JMX_CATEGORY_SHARD); } @@ -366,4 +370,19 @@ public class ShardStats extends AbstractMXBean implements ShardStatsMXBean { getOnDemandRaftState(); return statRetrievalError; } + + @Override + public long getLeadershipChangeCount() { + return leadershipChangeCount; + } + + public void incrementLeadershipChangeCount() { + leadershipChangeCount++; + lastLeadershipChangeTime = System.currentTimeMillis(); + } + + @Override + public String getLastLeadershipChangeTime() { + return DATE_FORMAT.format(new Date(lastLeadershipChangeTime)); + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/jmx/mbeans/shard/ShardStatsMXBean.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/jmx/mbeans/shard/ShardStatsMXBean.java index 1c0c83b699..12cea50e44 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/jmx/mbeans/shard/ShardStatsMXBean.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/jmx/mbeans/shard/ShardStatsMXBean.java @@ -79,4 +79,8 @@ public interface ShardStatsMXBean { List getFollowerInfo(); String getPeerAddresses(); + + long getLeadershipChangeCount(); + + String getLastLeadershipChangeTime(); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModifications.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModifications.java index a9ce94b033..86f96f57d0 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModifications.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModifications.java @@ -22,6 +22,7 @@ public class BatchedModifications extends MutableCompositeModification implement private static final long serialVersionUID = 1L; private boolean ready; + private int totalMessagesSent; private String transactionID; private String transactionChainID; @@ -42,6 +43,14 @@ public class BatchedModifications extends MutableCompositeModification implement this.ready = ready; } + public int getTotalMessagesSent() { + return totalMessagesSent; + } + + public void setTotalMessagesSent(int totalMessagesSent) { + this.totalMessagesSent = totalMessagesSent; + } + public String getTransactionID() { return transactionID; } @@ -56,6 +65,7 @@ public class BatchedModifications extends MutableCompositeModification implement transactionID = in.readUTF(); transactionChainID = in.readUTF(); ready = in.readBoolean(); + totalMessagesSent = in.readInt(); } @Override @@ -64,6 +74,7 @@ public class BatchedModifications extends MutableCompositeModification implement out.writeUTF(transactionID); out.writeUTF(transactionChainID); out.writeBoolean(ready); + out.writeInt(totalMessagesSent); } @Override @@ -74,8 +85,10 @@ public class BatchedModifications extends MutableCompositeModification implement @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("BatchedModifications [transactionID=").append(transactionID).append(", ready=").append(ready) - .append(", modifications size=").append(getModifications().size()).append("]"); + builder.append("BatchedModifications [transactionID=").append(transactionID).append(", transactionChainID=") + .append(transactionChainID).append(", ready=").append(ready).append(", totalMessagesSent=") + .append(totalMessagesSent).append(", modifications size=").append(getModifications().size()) + .append("]"); return builder.toString(); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModificationsReply.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModificationsReply.java index a10c6ac3fb..895de3a626 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModificationsReply.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModificationsReply.java @@ -19,11 +19,7 @@ import java.io.ObjectOutput; public class BatchedModificationsReply extends VersionedExternalizableMessage { private static final long serialVersionUID = 1L; - private static final byte COHORT_PATH_NOT_PRESENT = 0; - private static final byte COHORT_PATH_PRESENT = 1; - private int numBatched; - private String cohortPath; public BatchedModificationsReply() { } @@ -32,40 +28,20 @@ public class BatchedModificationsReply extends VersionedExternalizableMessage { this.numBatched = numBatched; } - public BatchedModificationsReply(int numBatched, String cohortPath) { - this.numBatched = numBatched; - this.cohortPath = cohortPath; - } - public int getNumBatched() { return numBatched; } - public String getCohortPath() { - return cohortPath; - } - @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { super.readExternal(in); numBatched = in.readInt(); - - if(in.readByte() == COHORT_PATH_PRESENT) { - cohortPath = in.readUTF(); - } } @Override public void writeExternal(ObjectOutput out) throws IOException { super.writeExternal(out); out.writeInt(numBatched); - - if(cohortPath != null) { - out.writeByte(COHORT_PATH_PRESENT); - out.writeUTF(cohortPath); - } else { - out.writeByte(COHORT_PATH_NOT_PRESENT); - } } @Override @@ -76,8 +52,7 @@ public class BatchedModificationsReply extends VersionedExternalizableMessage { @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("BatchedModificationsReply [numBatched=").append(numBatched).append(", cohortPath=") - .append(cohortPath).append("]"); + builder.append("BatchedModificationsReply [numBatched=").append(numBatched).append("]"); return builder.toString(); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ForwardedReadyTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ForwardedReadyTransaction.java index 38886c9a58..0f87243059 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ForwardedReadyTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ForwardedReadyTransaction.java @@ -20,9 +20,9 @@ public class ForwardedReadyTransaction { private final DOMStoreThreePhaseCommitCohort cohort; private final Modification modification; private final boolean returnSerialized; - private final int txnClientVersion; + private final short txnClientVersion; - public ForwardedReadyTransaction(String transactionID, int txnClientVersion, + public ForwardedReadyTransaction(String transactionID, short txnClientVersion, DOMStoreThreePhaseCommitCohort cohort, Modification modification, boolean returnSerialized) { this.transactionID = transactionID; @@ -48,7 +48,7 @@ public class ForwardedReadyTransaction { return returnSerialized; } - public int getTxnClientVersion() { + public short getTxnClientVersion() { return txnClientVersion; } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransaction.java index 09617abde9..8d617d0ba7 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransaction.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.cluster.datastore.messages; import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages; +@Deprecated public class ReadyTransaction implements SerializableMessage{ public static final Class SERIALIZABLE_CLASS = ShardTransactionMessages.ReadyTransaction.class; diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransactionReply.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransactionReply.java index 282e23ed3b..b25a5ddf29 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransactionReply.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransactionReply.java @@ -8,15 +8,29 @@ package org.opendaylight.controller.cluster.datastore.messages; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.opendaylight.controller.cluster.datastore.DataStoreVersions; import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages; -public class ReadyTransactionReply implements SerializableMessage { +public class ReadyTransactionReply extends VersionedExternalizableMessage { + private static final long serialVersionUID = 1L; + public static final Class SERIALIZABLE_CLASS = ShardTransactionMessages.ReadyTransactionReply.class; - private final String cohortPath; + private String cohortPath; + + public ReadyTransactionReply() { + } public ReadyTransactionReply(String cohortPath) { + this(cohortPath, DataStoreVersions.CURRENT_VERSION); + } + + public ReadyTransactionReply(String cohortPath, short version) { + super(version); this.cohortPath = cohortPath; } @@ -25,16 +39,38 @@ public class ReadyTransactionReply implements SerializableMessage { } @Override - public ShardTransactionMessages.ReadyTransactionReply toSerializable() { - return ShardTransactionMessages.ReadyTransactionReply.newBuilder() - .setActorPath(cohortPath) - .build(); + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + super.readExternal(in); + cohortPath = in.readUTF(); + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + super.writeExternal(out); + out.writeUTF(cohortPath); + } + + @Override + public Object toSerializable() { + if(getVersion() >= DataStoreVersions.LITHIUM_VERSION) { + return this; + } else { + return ShardTransactionMessages.ReadyTransactionReply.newBuilder().setActorPath(cohortPath).build(); + } } public static ReadyTransactionReply fromSerializable(Object serializable) { - ShardTransactionMessages.ReadyTransactionReply o = - (ShardTransactionMessages.ReadyTransactionReply) serializable; + if(serializable instanceof ReadyTransactionReply) { + return (ReadyTransactionReply)serializable; + } else { + ShardTransactionMessages.ReadyTransactionReply o = + (ShardTransactionMessages.ReadyTransactionReply) serializable; + return new ReadyTransactionReply(o.getActorPath(), DataStoreVersions.HELIUM_2_VERSION); + } + } - return new ReadyTransactionReply(o.getActorPath()); + public static boolean isSerializedType(Object message) { + return message instanceof ReadyTransactionReply || + message instanceof ShardTransactionMessages.ReadyTransactionReply; } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/modification/ModificationPayload.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/modification/ModificationPayload.java index 2e391570c4..01d11288d2 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/modification/ModificationPayload.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/modification/ModificationPayload.java @@ -66,7 +66,6 @@ public class ModificationPayload extends Payload implements Externalizable { out.write(serializedPayload); } - @SuppressWarnings("rawtypes") @Override @Deprecated public Map encode() { diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java index f53368d886..17d988005f 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java @@ -540,6 +540,10 @@ public class ActorContext { return this.dispatchers.getDispatcherPath(Dispatchers.DispatcherType.Notification); } + public Configuration getConfiguration() { + return configuration; + } + protected Future doAsk(ActorRef actorRef, Object message, Timeout timeout){ return ask(actorRef, message, timeout); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/MessageTracker.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/MessageTracker.java index 74e61c189f..d90cf500b0 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/MessageTracker.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/MessageTracker.java @@ -51,7 +51,7 @@ public class MessageTracker { private static final Context NO_OP_CONTEXT = new NoOpContext(); - private final Class expectedMessageClass; + private final Class expectedMessageClass; private final long expectedArrivalInterval; @@ -73,7 +73,7 @@ public class MessageTracker { * @param expectedArrivalIntervalInMillis The expected arrival interval between two instances of the expected * message */ - public MessageTracker(Class expectedMessageClass, long expectedArrivalIntervalInMillis){ + public MessageTracker(Class expectedMessageClass, long expectedArrivalIntervalInMillis){ this.expectedMessageClass = expectedMessageClass; this.expectedArrivalInterval = expectedArrivalIntervalInMillis; } @@ -120,10 +120,10 @@ public class MessageTracker { } public static class MessageProcessingTime { - private final Class messageClass; + private final Class messageClass; private final long elapsedTimeInNanos; - MessageProcessingTime(Class messageClass, long elapsedTimeInNanos){ + MessageProcessingTime(Class messageClass, long elapsedTimeInNanos){ this.messageClass = messageClass; this.elapsedTimeInNanos = elapsedTimeInNanos; } @@ -136,7 +136,7 @@ public class MessageTracker { '}'; } - public Class getMessageClass() { + public Class getMessageClass() { return messageClass; } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/NormalizedNodeAggregator.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/NormalizedNodeAggregator.java new file mode 100644 index 0000000000..a406b9aaf5 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/NormalizedNodeAggregator.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.datastore.utils; + +import com.google.common.base.Optional; +import java.util.List; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; +import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class NormalizedNodeAggregator { + private final YangInstanceIdentifier rootIdentifier; + private final List>> nodes; + private final DataTree dataTree; + + private NormalizedNodeAggregator(YangInstanceIdentifier rootIdentifier, List>> nodes, + SchemaContext schemaContext) { + this.rootIdentifier = rootIdentifier; + this.nodes = nodes; + this.dataTree = InMemoryDataTreeFactory.getInstance().create(); + this.dataTree.setSchemaContext(schemaContext); + } + + /** + * Combine data from all the nodes in the list into a tree with root as rootIdentifier + * + * @param nodes + * @param schemaContext + * @return + * @throws DataValidationFailedException + */ + public static Optional> aggregate(YangInstanceIdentifier rootIdentifier, + List>> nodes, + SchemaContext schemaContext) throws DataValidationFailedException { + return new NormalizedNodeAggregator(rootIdentifier, nodes, schemaContext).aggregate(); + } + + private Optional> aggregate() throws DataValidationFailedException { + return combine().getRootNode(); + } + + private NormalizedNodeAggregator combine() throws DataValidationFailedException { + DataTreeModification mod = dataTree.takeSnapshot().newModification(); + + for (Optional> node : nodes) { + if (node.isPresent()) { + mod.merge(rootIdentifier, node.get()); + } + } + + dataTree.validate(mod); + final DataTreeCandidate candidate = dataTree.prepare(mod); + dataTree.commit(candidate); + + return this; + } + + private Optional> getRootNode() { + return dataTree.takeSnapshot().readNode(rootIdentifier); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractTransactionProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractTransactionProxyTest.java index c6c5486ee3..6a1e12a96b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractTransactionProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/AbstractTransactionProxyTest.java @@ -50,7 +50,6 @@ import org.opendaylight.controller.cluster.datastore.messages.DataExists; import org.opendaylight.controller.cluster.datastore.messages.DataExistsReply; import org.opendaylight.controller.cluster.datastore.messages.ReadData; import org.opendaylight.controller.cluster.datastore.messages.ReadDataReply; -import org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction; import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply; import org.opendaylight.controller.cluster.datastore.modification.AbstractModification; import org.opendaylight.controller.cluster.datastore.modification.Modification; @@ -204,10 +203,6 @@ public abstract class AbstractTransactionProxyTest { return argThat(matcher); } - protected Future readySerializedTxReply(String path) { - return Futures.successful((Object)new ReadyTransactionReply(path).toSerializable()); - } - protected Future readyTxReply(String path) { return Futures.successful((Object)new ReadyTransactionReply(path)); } @@ -250,10 +245,8 @@ public abstract class AbstractTransactionProxyTest { eq(actorSelection(actorRef)), isA(BatchedModifications.class)); } - protected void expectBatchedModificationsReady(ActorRef actorRef, int count) { - Future replyFuture = Futures.successful( - new BatchedModificationsReply(count, actorRef.path().toString())); - doReturn(replyFuture).when(mockActorContext).executeOperationAsync( + protected void expectBatchedModificationsReady(ActorRef actorRef) { + doReturn(readyTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( eq(actorSelection(actorRef)), isA(BatchedModifications.class)); } @@ -267,11 +260,6 @@ public abstract class AbstractTransactionProxyTest { any(ActorSelection.class), isA(BatchedModifications.class)); } - protected void expectReadyTransaction(ActorRef actorRef) { - doReturn(readySerializedTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), isA(ReadyTransaction.SERIALIZABLE_CLASS)); - } - protected void expectFailedBatchedModifications(ActorRef actorRef) { doReturn(Futures.failed(new TestException())).when(mockActorContext).executeOperationAsync( eq(actorSelection(actorRef)), isA(BatchedModifications.class)); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java index e04c1a5d18..e3b82df174 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java @@ -17,6 +17,7 @@ import akka.dispatch.Dispatchers; import akka.dispatch.OnComplete; import akka.japi.Creator; import akka.pattern.Patterns; +import akka.persistence.SaveSnapshotSuccess; import akka.testkit.TestActorRef; import akka.util.Timeout; import com.google.common.base.Function; @@ -436,42 +437,42 @@ public class ShardTest extends AbstractShardTest { waitUntilLeader(shard); - final String transactionID1 = "tx1"; - final String transactionID2 = "tx2"; - final String transactionID3 = "tx3"; + // Setup 3 simulated transactions with mock cohorts backed by real cohorts. - final AtomicReference mockCohort1 = new AtomicReference<>(); - final AtomicReference mockCohort2 = new AtomicReference<>(); - final AtomicReference mockCohort3 = new AtomicReference<>(); - ShardCommitCoordinator.CohortDecorator cohortDecorator = new ShardCommitCoordinator.CohortDecorator() { - @Override - public DOMStoreThreePhaseCommitCohort decorate(String transactionID, DOMStoreThreePhaseCommitCohort actual) { - if(transactionID.equals(transactionID1)) { - mockCohort1.set(createDelegatingMockCohort("cohort1", actual)); - return mockCohort1.get(); - } else if(transactionID.equals(transactionID2)) { - mockCohort2.set(createDelegatingMockCohort("cohort2", actual)); - return mockCohort2.get(); - } else { - mockCohort3.set(createDelegatingMockCohort("cohort3", actual)); - return mockCohort3.get(); - } - } - }; + InMemoryDOMDataStore dataStore = shard.underlyingActor().getDataStore(); - shard.underlyingActor().getCommitCoordinator().setCohortDecorator(cohortDecorator); + String transactionID1 = "tx1"; + MutableCompositeModification modification1 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort1 = setupMockWriteTransaction("cohort1", dataStore, + TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME), modification1); + + String transactionID2 = "tx2"; + MutableCompositeModification modification2 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort2 = setupMockWriteTransaction("cohort2", dataStore, + TestModel.OUTER_LIST_PATH, + ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build(), + modification2); + + String transactionID3 = "tx3"; + MutableCompositeModification modification3 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort3 = setupMockWriteTransaction("cohort3", dataStore, + YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH) + .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1).build(), + ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1), + modification3); long timeoutSec = 5; final FiniteDuration duration = FiniteDuration.create(timeoutSec, TimeUnit.SECONDS); final Timeout timeout = new Timeout(duration); - // Send a BatchedModifications message for the first transaction. + // Simulate the ForwardedReadyTransaction message for the first Tx that would be sent + // by the ShardTransaction. - shard.tell(newBatchedModifications(transactionID1, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - BatchedModificationsReply batchedReply = expectMsgClass(duration, BatchedModificationsReply.class); - assertEquals("getCohortPath", shard.path().toString(), batchedReply.getCohortPath()); - assertEquals("getNumBatched", 1, batchedReply.getNumBatched()); + shard.tell(new ForwardedReadyTransaction(transactionID1, CURRENT_VERSION, + cohort1, modification1, true), getRef()); + ReadyTransactionReply readyReply = ReadyTransactionReply.fromSerializable( + expectMsgClass(duration, ReadyTransactionReply.class)); + assertEquals("Cohort path", shard.path().toString(), readyReply.getCohortPath()); // Send the CanCommitTransaction message for the first Tx. @@ -480,16 +481,15 @@ public class ShardTest extends AbstractShardTest { expectMsgClass(duration, CanCommitTransactionReply.SERIALIZABLE_CLASS)); assertEquals("Can commit", true, canCommitReply.getCanCommit()); - // Send BatchedModifications for the next 2 Tx's. + // Send the ForwardedReadyTransaction for the next 2 Tx's. - shard.tell(newBatchedModifications(transactionID2, TestModel.OUTER_LIST_PATH, - ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build(), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID2, CURRENT_VERSION, + cohort2, modification2, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); - shard.tell(newBatchedModifications(transactionID3, YangInstanceIdentifier.builder( - TestModel.OUTER_LIST_PATH).nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1).build(), - ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID3, CURRENT_VERSION, + cohort3, modification3, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message for the next 2 Tx's. These should get queued and // processed after the first Tx completes. @@ -582,16 +582,16 @@ public class ShardTest extends AbstractShardTest { assertEquals("Commits complete", true, done); - InOrder inOrder = inOrder(mockCohort1.get(), mockCohort2.get(), mockCohort3.get()); - inOrder.verify(mockCohort1.get()).canCommit(); - inOrder.verify(mockCohort1.get()).preCommit(); - inOrder.verify(mockCohort1.get()).commit(); - inOrder.verify(mockCohort2.get()).canCommit(); - inOrder.verify(mockCohort2.get()).preCommit(); - inOrder.verify(mockCohort2.get()).commit(); - inOrder.verify(mockCohort3.get()).canCommit(); - inOrder.verify(mockCohort3.get()).preCommit(); - inOrder.verify(mockCohort3.get()).commit(); + InOrder inOrder = inOrder(cohort1, cohort2, cohort3); + inOrder.verify(cohort1).canCommit(); + inOrder.verify(cohort1).preCommit(); + inOrder.verify(cohort1).commit(); + inOrder.verify(cohort2).canCommit(); + inOrder.verify(cohort2).preCommit(); + inOrder.verify(cohort2).commit(); + inOrder.verify(cohort3).canCommit(); + inOrder.verify(cohort3).preCommit(); + inOrder.verify(cohort3).commit(); // Verify data in the data store. @@ -669,7 +669,7 @@ public class ShardTest extends AbstractShardTest { shard.tell(newBatchedModifications(transactionID, YangInstanceIdentifier.builder( TestModel.OUTER_LIST_PATH).nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1).build(), ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message. @@ -728,7 +728,7 @@ public class ShardTest extends AbstractShardTest { YangInstanceIdentifier path = TestModel.TEST_PATH; shard.tell(newBatchedModifications(transactionID1, transactionChainID, path, containerNode, true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + expectMsgClass(duration, ReadyTransactionReply.class); // Create a read Tx on the same chain. @@ -810,14 +810,24 @@ public class ShardTest extends AbstractShardTest { waitUntilLeader(shard); + InMemoryDOMDataStore dataStore = shard.underlyingActor().getDataStore(); + + // Setup a simulated transactions with a mock cohort. + String transactionID = "tx"; + MutableCompositeModification modification = new MutableCompositeModification(); + NormalizedNode containerNode = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + DOMStoreThreePhaseCommitCohort cohort = setupMockWriteTransaction("cohort", dataStore, + TestModel.TEST_PATH, containerNode, modification); + FiniteDuration duration = duration("5 seconds"); - // Send a BatchedModifications to start a transaction. + // Simulate the ForwardedReadyTransaction messages that would be sent + // by the ShardTransaction. - NormalizedNode containerNode = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - shard.tell(newBatchedModifications(transactionID, TestModel.TEST_PATH, containerNode, true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID, CURRENT_VERSION, + cohort, modification, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message. @@ -831,6 +841,11 @@ public class ShardTest extends AbstractShardTest { shard.tell(new CommitTransaction(transactionID).toSerializable(), getRef()); expectMsgClass(duration, CommitTransactionReply.SERIALIZABLE_CLASS); + InOrder inOrder = inOrder(cohort); + inOrder.verify(cohort).canCommit(); + inOrder.verify(cohort).preCommit(); + inOrder.verify(cohort).commit(); + NormalizedNode actualNode = readStore(shard, TestModel.TEST_PATH); assertEquals(TestModel.TEST_QNAME.getLocalName(), containerNode, actualNode); @@ -864,7 +879,7 @@ public class ShardTest extends AbstractShardTest { shard.tell(new ForwardedReadyTransaction(transactionID, CURRENT_VERSION, cohort, modification, true), getRef()); - expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message. @@ -919,7 +934,7 @@ public class ShardTest extends AbstractShardTest { shard.tell(new ForwardedReadyTransaction(transactionID, CURRENT_VERSION, cohort, modification, true), getRef()); - expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message. @@ -958,40 +973,34 @@ public class ShardTest extends AbstractShardTest { waitUntilLeader(shard); - // Setup 2 mock cohorts. The first one fails in the commit phase. + // Setup 2 simulated transactions with mock cohorts. The first one fails in the + // commit phase. - final String transactionID1 = "tx1"; - final DOMStoreThreePhaseCommitCohort cohort1 = mock(DOMStoreThreePhaseCommitCohort.class, "cohort1"); + String transactionID1 = "tx1"; + MutableCompositeModification modification1 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort1 = mock(DOMStoreThreePhaseCommitCohort.class, "cohort1"); doReturn(Futures.immediateFuture(Boolean.TRUE)).when(cohort1).canCommit(); doReturn(Futures.immediateFuture(null)).when(cohort1).preCommit(); doReturn(Futures.immediateFailedFuture(new IllegalStateException("mock"))).when(cohort1).commit(); - final String transactionID2 = "tx2"; - final DOMStoreThreePhaseCommitCohort cohort2 = mock(DOMStoreThreePhaseCommitCohort.class, "cohort2"); + String transactionID2 = "tx2"; + MutableCompositeModification modification2 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort2 = mock(DOMStoreThreePhaseCommitCohort.class, "cohort2"); doReturn(Futures.immediateFuture(Boolean.TRUE)).when(cohort2).canCommit(); - ShardCommitCoordinator.CohortDecorator cohortDecorator = new ShardCommitCoordinator.CohortDecorator() { - @Override - public DOMStoreThreePhaseCommitCohort decorate(String transactionID, - DOMStoreThreePhaseCommitCohort actual) { - return transactionID1.equals(transactionID) ? cohort1 : cohort2; - } - }; - - shard.underlyingActor().getCommitCoordinator().setCohortDecorator(cohortDecorator); - FiniteDuration duration = duration("5 seconds"); final Timeout timeout = new Timeout(duration); - // Send BatchedModifications to start and ready each transaction. + // Simulate the ForwardedReadyTransaction messages that would be sent + // by the ShardTransaction. - shard.tell(newBatchedModifications(transactionID1, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID1, CURRENT_VERSION, + cohort1, modification1, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); - shard.tell(newBatchedModifications(transactionID2, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID2, CURRENT_VERSION, + cohort2, modification2, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message for the first Tx. @@ -1044,27 +1053,19 @@ public class ShardTest extends AbstractShardTest { waitUntilLeader(shard); String transactionID = "tx1"; - final DOMStoreThreePhaseCommitCohort cohort = mock(DOMStoreThreePhaseCommitCohort.class, "cohort1"); + MutableCompositeModification modification = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort = mock(DOMStoreThreePhaseCommitCohort.class, "cohort1"); doReturn(Futures.immediateFuture(Boolean.TRUE)).when(cohort).canCommit(); doReturn(Futures.immediateFailedFuture(new IllegalStateException("mock"))).when(cohort).preCommit(); - ShardCommitCoordinator.CohortDecorator cohortDecorator = new ShardCommitCoordinator.CohortDecorator() { - @Override - public DOMStoreThreePhaseCommitCohort decorate(String transactionID, - DOMStoreThreePhaseCommitCohort actual) { - return cohort; - } - }; - - shard.underlyingActor().getCommitCoordinator().setCohortDecorator(cohortDecorator); - FiniteDuration duration = duration("5 seconds"); - // Send BatchedModifications to start and ready a transaction. + // Simulate the ForwardedReadyTransaction messages that would be sent + // by the ShardTransaction. - shard.tell(newBatchedModifications(transactionID, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID, CURRENT_VERSION, + cohort, modification, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message. @@ -1099,24 +1100,16 @@ public class ShardTest extends AbstractShardTest { final FiniteDuration duration = duration("5 seconds"); String transactionID = "tx1"; - final DOMStoreThreePhaseCommitCohort cohort = mock(DOMStoreThreePhaseCommitCohort.class, "cohort1"); + MutableCompositeModification modification = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort = mock(DOMStoreThreePhaseCommitCohort.class, "cohort1"); doReturn(Futures.immediateFailedFuture(new IllegalStateException("mock"))).when(cohort).canCommit(); - ShardCommitCoordinator.CohortDecorator cohortDecorator = new ShardCommitCoordinator.CohortDecorator() { - @Override - public DOMStoreThreePhaseCommitCohort decorate(String transactionID, - DOMStoreThreePhaseCommitCohort actual) { - return cohort; - } - }; + // Simulate the ForwardedReadyTransaction messages that would be sent + // by the ShardTransaction. - shard.underlyingActor().getCommitCoordinator().setCohortDecorator(cohortDecorator); - - // Send BatchedModifications to start and ready a transaction. - - shard.tell(newBatchedModifications(transactionID, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID, CURRENT_VERSION, + cohort, modification, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message. @@ -1160,9 +1153,14 @@ public class ShardTest extends AbstractShardTest { } }; - shard.tell(newBatchedModifications(transactionID, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + MutableCompositeModification modification = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort = setupMockWriteTransaction("cohort1", dataStore, + TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME), + modification, preCommit); + + shard.tell(new ForwardedReadyTransaction(transactionID, CURRENT_VERSION, + cohort, modification, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); shard.tell(new CanCommitTransaction(transactionID).toSerializable(), getRef()); CanCommitTransactionReply canCommitReply = CanCommitTransactionReply.fromSerializable( @@ -1196,26 +1194,42 @@ public class ShardTest extends AbstractShardTest { final FiniteDuration duration = duration("5 seconds"); + InMemoryDOMDataStore dataStore = shard.underlyingActor().getDataStore(); + writeToStore(shard, TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME)); writeToStore(shard, TestModel.OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build()); - // Create and ready the 1st Tx - will timeout + // Create 1st Tx - will timeout String transactionID1 = "tx1"; - shard.tell(newBatchedModifications(transactionID1, YangInstanceIdentifier.builder( - TestModel.OUTER_LIST_PATH).nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1).build(), - ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + MutableCompositeModification modification1 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort1 = setupMockWriteTransaction("cohort1", dataStore, + YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH) + .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1).build(), + ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1), + modification1); - // Create and ready the 2nd Tx + // Create 2nd Tx - String transactionID2 = "tx2"; + String transactionID2 = "tx3"; + MutableCompositeModification modification2 = new MutableCompositeModification(); YangInstanceIdentifier listNodePath = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH) - .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2).build(); - shard.tell(newBatchedModifications(transactionID2, listNodePath, - ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + .nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2).build(); + DOMStoreThreePhaseCommitCohort cohort2 = setupMockWriteTransaction("cohort3", dataStore, + listNodePath, + ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 2), + modification2); + + // Ready the Tx's + + shard.tell(new ForwardedReadyTransaction(transactionID1, CURRENT_VERSION, + cohort1, modification1, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); + + shard.tell(new ForwardedReadyTransaction(transactionID2, CURRENT_VERSION, + cohort2, modification2, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); // canCommit 1st Tx. We don't send the commit so it should timeout. @@ -1252,23 +1266,38 @@ public class ShardTest extends AbstractShardTest { final FiniteDuration duration = duration("5 seconds"); + InMemoryDOMDataStore dataStore = shard.underlyingActor().getDataStore(); + String transactionID1 = "tx1"; + MutableCompositeModification modification1 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort1 = setupMockWriteTransaction("cohort1", dataStore, + TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME), modification1); + String transactionID2 = "tx2"; + MutableCompositeModification modification2 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort2 = setupMockWriteTransaction("cohort2", dataStore, + TestModel.OUTER_LIST_PATH, + ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build(), + modification2); + String transactionID3 = "tx3"; + MutableCompositeModification modification3 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort3 = setupMockWriteTransaction("cohort3", dataStore, + TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME), modification3); - // Send a BatchedModifications to start transactions and ready them. + // Ready the Tx's - shard.tell(newBatchedModifications(transactionID1, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID1, CURRENT_VERSION, + cohort1, modification1, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); - shard.tell(newBatchedModifications(transactionID2,TestModel.OUTER_LIST_PATH, - ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build(), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID2, CURRENT_VERSION, + cohort2, modification2, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); - shard.tell(newBatchedModifications(transactionID3, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID3, CURRENT_VERSION, + cohort3, modification3, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); // canCommit 1st Tx. @@ -1313,37 +1342,30 @@ public class ShardTest extends AbstractShardTest { // Setup 2 simulated transactions with mock cohorts. The first one will be aborted. - final String transactionID1 = "tx1"; - final DOMStoreThreePhaseCommitCohort cohort1 = mock(DOMStoreThreePhaseCommitCohort.class, "cohort1"); + String transactionID1 = "tx1"; + MutableCompositeModification modification1 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort1 = mock(DOMStoreThreePhaseCommitCohort.class, "cohort1"); doReturn(Futures.immediateFuture(Boolean.TRUE)).when(cohort1).canCommit(); doReturn(Futures.immediateFuture(null)).when(cohort1).abort(); - final String transactionID2 = "tx2"; - final DOMStoreThreePhaseCommitCohort cohort2 = mock(DOMStoreThreePhaseCommitCohort.class, "cohort2"); + String transactionID2 = "tx2"; + MutableCompositeModification modification2 = new MutableCompositeModification(); + DOMStoreThreePhaseCommitCohort cohort2 = mock(DOMStoreThreePhaseCommitCohort.class, "cohort2"); doReturn(Futures.immediateFuture(Boolean.TRUE)).when(cohort2).canCommit(); FiniteDuration duration = duration("5 seconds"); final Timeout timeout = new Timeout(duration); - ShardCommitCoordinator.CohortDecorator cohortDecorator = new ShardCommitCoordinator.CohortDecorator() { - @Override - public DOMStoreThreePhaseCommitCohort decorate(String transactionID, - DOMStoreThreePhaseCommitCohort actual) { - return transactionID1.equals(transactionID) ? cohort1 : cohort2; - } - }; - - shard.underlyingActor().getCommitCoordinator().setCohortDecorator(cohortDecorator); - - // Send BatchedModifications to start and ready each transaction. + // Simulate the ForwardedReadyTransaction messages that would be sent + // by the ShardTransaction. - shard.tell(newBatchedModifications(transactionID1, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID1, CURRENT_VERSION, + cohort1, modification1, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); - shard.tell(newBatchedModifications(transactionID2, TestModel.TEST_PATH, - ImmutableNodes.containerNode(TestModel.TEST_QNAME), true), getRef()); - expectMsgClass(duration, BatchedModificationsReply.class); + shard.tell(new ForwardedReadyTransaction(transactionID2, CURRENT_VERSION, + cohort2, modification2, true), getRef()); + expectMsgClass(duration, ReadyTransactionReply.class); // Send the CanCommitTransaction message for the first Tx. @@ -1389,6 +1411,8 @@ public class ShardTest extends AbstractShardTest { @SuppressWarnings("serial") public void testCreateSnapshot(final boolean persistent, final String shardActorName) throws Exception{ + final AtomicReference latch = new AtomicReference<>(new CountDownLatch(1)); + final AtomicReference savedSnapshot = new AtomicReference<>(); class TestPersistentDataProvider extends DelegatingPersistentDataProvider { TestPersistentDataProvider(DataPersistenceProvider delegate) { @@ -1405,8 +1429,6 @@ public class ShardTest extends AbstractShardTest { dataStoreContextBuilder.persistent(persistent); new ShardTestKit(getSystem()) {{ - final AtomicReference latch = new AtomicReference<>(new CountDownLatch(1)); - class TestShard extends Shard { protected TestShard(ShardIdentifier name, Map peerAddresses, @@ -1416,9 +1438,12 @@ public class ShardTest extends AbstractShardTest { } @Override - protected void commitSnapshot(final long sequenceNumber) { - super.commitSnapshot(sequenceNumber); - latch.get().countDown(); + public void handleCommand(Object message) { + super.handleCommand(message); + + if (message instanceof SaveSnapshotSuccess || message.equals("commit_snapshot")) { + latch.get().countDown(); + } } @Override diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionFailureTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionFailureTest.java index c3fef611e3..21fb55fcf1 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionFailureTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionFailureTest.java @@ -70,8 +70,7 @@ public class ShardTransactionFailureTest extends AbstractActorTest { final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newReadOnlyTransaction(), shard, - testSchemaContext, datastoreContext, shardStats, "txn", - DataStoreVersions.CURRENT_VERSION); + datastoreContext, shardStats, "txn", DataStoreVersions.CURRENT_VERSION); final TestActorRef subject = TestActorRef .create(getSystem(), props, @@ -100,8 +99,7 @@ public class ShardTransactionFailureTest extends AbstractActorTest { final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard, - testSchemaContext, datastoreContext, shardStats, "txn", - DataStoreVersions.CURRENT_VERSION); + datastoreContext, shardStats, "txn", DataStoreVersions.CURRENT_VERSION); final TestActorRef subject = TestActorRef .create(getSystem(), props, @@ -130,8 +128,7 @@ public class ShardTransactionFailureTest extends AbstractActorTest { final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard, - testSchemaContext, datastoreContext, shardStats, "txn", - DataStoreVersions.CURRENT_VERSION); + datastoreContext, shardStats, "txn", DataStoreVersions.CURRENT_VERSION); final TestActorRef subject = TestActorRef .create(getSystem(), props, @@ -160,8 +157,7 @@ public class ShardTransactionFailureTest extends AbstractActorTest { final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newWriteOnlyTransaction(), shard, - testSchemaContext, datastoreContext, shardStats, "txn", - DataStoreVersions.CURRENT_VERSION); + datastoreContext, shardStats, "txn", DataStoreVersions.CURRENT_VERSION); final TestActorRef subject = TestActorRef .create(getSystem(), props, @@ -193,8 +189,7 @@ public class ShardTransactionFailureTest extends AbstractActorTest { final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard, - testSchemaContext, datastoreContext, shardStats, "txn", - DataStoreVersions.CURRENT_VERSION); + datastoreContext, shardStats, "txn", DataStoreVersions.CURRENT_VERSION); final TestActorRef subject = TestActorRef .create(getSystem(), props, @@ -231,8 +226,7 @@ public class ShardTransactionFailureTest extends AbstractActorTest { final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard, - testSchemaContext, datastoreContext, shardStats, "txn", - DataStoreVersions.CURRENT_VERSION); + datastoreContext, shardStats, "txn", DataStoreVersions.CURRENT_VERSION); final TestActorRef subject = TestActorRef .create(getSystem(), props, "testNegativeMergeTransactionReady"); @@ -264,8 +258,7 @@ public class ShardTransactionFailureTest extends AbstractActorTest { final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard, - testSchemaContext, datastoreContext, shardStats, "txn", - DataStoreVersions.CURRENT_VERSION); + datastoreContext, shardStats, "txn", DataStoreVersions.CURRENT_VERSION); final TestActorRef subject = TestActorRef .create(getSystem(), props, diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java index e63ace3e2c..c9335f378a 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java @@ -4,8 +4,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; import akka.actor.ActorRef; import akka.actor.Props; +import akka.actor.Status.Failure; import akka.actor.Terminated; import akka.testkit.JavaTestKit; import akka.testkit.TestActorRef; @@ -52,6 +54,7 @@ import org.opendaylight.controller.protobuff.messages.transaction.ShardTransacti import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction; import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; @@ -97,7 +100,7 @@ public class ShardTransactionTest extends AbstractActorTest { private ActorRef newTransactionActor(DOMStoreTransaction transaction, ActorRef shard, String name, short version) { Props props = ShardTransaction.props(transaction, shard != null ? shard : createShard(), - testSchemaContext, datastoreContext, shardStats, "txn", version); + datastoreContext, shardStats, "txn", version); return getSystem().actorOf(props, name); } @@ -409,34 +412,125 @@ public class ShardTransactionTest extends AbstractActorTest { } @Test - public void testOnReceiveReadyTransaction() throws Exception { + public void testOnReceiveBatchedModificationsReady() throws Exception { + new JavaTestKit(getSystem()) {{ + + final ActorRef transaction = newTransactionActor(store.newWriteOnlyTransaction(), + "testOnReceiveBatchedModificationsReady"); + + JavaTestKit watcher = new JavaTestKit(getSystem()); + watcher.watch(transaction); + + YangInstanceIdentifier writePath = TestModel.TEST_PATH; + NormalizedNode writeData = ImmutableContainerNodeBuilder.create().withNodeIdentifier( + new YangInstanceIdentifier.NodeIdentifier(TestModel.TEST_QNAME)). + withChild(ImmutableNodes.leafNode(TestModel.DESC_QNAME, "foo")).build(); + + BatchedModifications batched = new BatchedModifications("tx1", DataStoreVersions.CURRENT_VERSION, null); + batched.addModification(new WriteModification(writePath, writeData)); + + transaction.tell(batched, getRef()); + BatchedModificationsReply reply = expectMsgClass(duration("5 seconds"), BatchedModificationsReply.class); + assertEquals("getNumBatched", 1, reply.getNumBatched()); + + batched = new BatchedModifications("tx1", DataStoreVersions.CURRENT_VERSION, null); + batched.setReady(true); + batched.setTotalMessagesSent(2); + + transaction.tell(batched, getRef()); + expectMsgClass(duration("5 seconds"), ReadyTransactionReply.class); + watcher.expectMsgClass(duration("5 seconds"), Terminated.class); + }}; + } + + @Test(expected=TestException.class) + public void testOnReceiveBatchedModificationsFailure() throws Throwable { + new JavaTestKit(getSystem()) {{ + + DOMStoreWriteTransaction mockWriteTx = Mockito.mock(DOMStoreWriteTransaction.class); + final ActorRef transaction = newTransactionActor(mockWriteTx, + "testOnReceiveBatchedModificationsFailure"); + + JavaTestKit watcher = new JavaTestKit(getSystem()); + watcher.watch(transaction); + + YangInstanceIdentifier path = TestModel.TEST_PATH; + ContainerNode node = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + + doThrow(new TestException()).when(mockWriteTx).write(path, node); + + BatchedModifications batched = new BatchedModifications("tx1", DataStoreVersions.CURRENT_VERSION, null); + batched.addModification(new WriteModification(path, node)); + + transaction.tell(batched, getRef()); + expectMsgClass(duration("5 seconds"), akka.actor.Status.Failure.class); + + batched = new BatchedModifications("tx1", DataStoreVersions.CURRENT_VERSION, null); + batched.setReady(true); + batched.setTotalMessagesSent(2); + + transaction.tell(batched, getRef()); + Failure failure = expectMsgClass(duration("5 seconds"), akka.actor.Status.Failure.class); + watcher.expectMsgClass(duration("5 seconds"), Terminated.class); + + if(failure != null) { + throw failure.cause(); + } + }}; + } + + @Test(expected=IllegalStateException.class) + public void testOnReceiveBatchedModificationsReadyWithIncorrectTotalMessageCount() throws Throwable { + new JavaTestKit(getSystem()) {{ + + final ActorRef transaction = newTransactionActor(store.newWriteOnlyTransaction(), + "testOnReceiveBatchedModificationsReadyWithIncorrectTotalMessageCount"); + + JavaTestKit watcher = new JavaTestKit(getSystem()); + watcher.watch(transaction); + + BatchedModifications batched = new BatchedModifications("tx1", DataStoreVersions.CURRENT_VERSION, null); + batched.setReady(true); + batched.setTotalMessagesSent(2); + + transaction.tell(batched, getRef()); + + Failure failure = expectMsgClass(duration("5 seconds"), akka.actor.Status.Failure.class); + watcher.expectMsgClass(duration("5 seconds"), Terminated.class); + + if(failure != null) { + throw failure.cause(); + } + }}; + } + + @Test + public void testOnReceivePreLithiumReadyTransaction() throws Exception { new JavaTestKit(getSystem()) {{ final ActorRef transaction = newTransactionActor(store.newReadWriteTransaction(), - "testReadyTransaction"); + "testReadyTransaction", DataStoreVersions.HELIUM_2_VERSION); - watch(transaction); + JavaTestKit watcher = new JavaTestKit(getSystem()); + watcher.watch(transaction); transaction.tell(new ReadyTransaction().toSerializable(), getRef()); - expectMsgAnyClassOf(duration("5 seconds"), ReadyTransactionReply.SERIALIZABLE_CLASS, - Terminated.class); - expectMsgAnyClassOf(duration("5 seconds"), ReadyTransactionReply.SERIALIZABLE_CLASS, - Terminated.class); + expectMsgClass(duration("5 seconds"), ReadyTransactionReply.SERIALIZABLE_CLASS); + watcher.expectMsgClass(duration("5 seconds"), Terminated.class); }}; // test new JavaTestKit(getSystem()) {{ final ActorRef transaction = newTransactionActor(store.newReadWriteTransaction(), - "testReadyTransaction2"); + "testReadyTransaction2", DataStoreVersions.HELIUM_2_VERSION); - watch(transaction); + JavaTestKit watcher = new JavaTestKit(getSystem()); + watcher.watch(transaction); transaction.tell(new ReadyTransaction(), getRef()); - expectMsgAnyClassOf(duration("5 seconds"), ReadyTransactionReply.class, - Terminated.class); - expectMsgAnyClassOf(duration("5 seconds"), ReadyTransactionReply.class, - Terminated.class); + expectMsgClass(duration("5 seconds"), ReadyTransactionReply.class); + watcher.expectMsgClass(duration("5 seconds"), Terminated.class); }}; } @@ -517,8 +611,7 @@ public class ShardTransactionTest extends AbstractActorTest { public void testNegativePerformingWriteOperationOnReadTransaction() throws Exception { final ActorRef shard = createShard(); final Props props = ShardTransaction.props(store.newReadOnlyTransaction(), shard, - testSchemaContext, datastoreContext, shardStats, "txn", - DataStoreVersions.CURRENT_VERSION); + datastoreContext, shardStats, "txn", DataStoreVersions.CURRENT_VERSION); final TestActorRef transaction = TestActorRef.apply(props,getSystem()); transaction.receive(new DeleteData(TestModel.TEST_PATH, DataStoreVersions.CURRENT_VERSION). @@ -540,4 +633,8 @@ public class ShardTransactionTest extends AbstractActorTest { expectMsgClass(duration("3 seconds"), Terminated.class); }}; } + + public static class TestException extends RuntimeException { + private static final long serialVersionUID = 1L; + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxyTest.java index acba775445..026b549028 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxyTest.java @@ -30,8 +30,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.junit.Assert; import org.junit.Test; import org.opendaylight.controller.cluster.datastore.messages.BatchedModifications; -import org.opendaylight.controller.cluster.datastore.messages.BatchedModificationsReply; -import org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction; import org.opendaylight.controller.cluster.datastore.modification.WriteModification; import org.opendaylight.controller.cluster.datastore.shardstrategy.DefaultShardStrategy; import org.opendaylight.controller.cluster.datastore.utils.ActorContext; @@ -176,7 +174,7 @@ public class TransactionChainProxyTest extends AbstractTransactionProxyTest { fail("Tx 2 should not have initiated until the Tx 1's ready future completed"); } - batchedReplyPromise1.success(new BatchedModificationsReply(1, txActorRef1.path().toString())); + batchedReplyPromise1.success(readyTxReply(txActorRef1.path().toString()).value().get().get()); // Tx 2 should've proceeded to find the primary shard. verify(mockActorContext, timeout(5000).times(2)).findPrimaryShardAsync(eq(DefaultShardStrategy.DEFAULT_SHARD)); @@ -196,7 +194,7 @@ public class TransactionChainProxyTest extends AbstractTransactionProxyTest { Promise readyReplyPromise1 = akka.dispatch.Futures.promise(); doReturn(readyReplyPromise1.future()).when(mockActorContext).executeOperationAsync( - eq(actorSelection(txActorRef1)), isA(ReadyTransaction.SERIALIZABLE_CLASS)); + eq(actorSelection(txActorRef1)), isA(BatchedModifications.class)); DOMStoreWriteTransaction writeTx1 = txChainProxy.newReadWriteTransaction(); @@ -205,7 +203,7 @@ public class TransactionChainProxyTest extends AbstractTransactionProxyTest { writeTx1.ready(); - verifyOneBatchedModification(txActorRef1, new WriteModification(TestModel.TEST_PATH, writeNode1), false); + verifyOneBatchedModification(txActorRef1, new WriteModification(TestModel.TEST_PATH, writeNode1), true); String tx2MemberName = "tx2MemberName"; doReturn(tx2MemberName).when(mockActorContext).getCurrentMemberName(); @@ -247,7 +245,7 @@ public class TransactionChainProxyTest extends AbstractTransactionProxyTest { fail("Tx 2 should not have initiated until the Tx 1's ready future completed"); } - readyReplyPromise1.success(readySerializedTxReply(txActorRef1.path().toString()).value().get().get()); + readyReplyPromise1.success(readyTxReply(txActorRef1.path().toString()).value().get().get()); verify(mockActorContext, timeout(5000)).executeOperationAsync(eq(getSystem().actorSelection(shardActorRef2.path())), eqCreateTransaction(tx2MemberName, READ_WRITE)); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java index a247100186..cc9692bfd9 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java @@ -3,12 +3,12 @@ package org.opendaylight.controller.cluster.datastore; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.opendaylight.controller.cluster.datastore.TransactionProxy.TransactionType.READ_ONLY; @@ -20,11 +20,14 @@ import akka.actor.ActorSystem; import akka.actor.Props; import akka.dispatch.Futures; import com.google.common.base.Optional; +import com.google.common.collect.Sets; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Uninterruptibles; +import java.util.Collection; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.Assert; @@ -37,7 +40,6 @@ import org.opendaylight.controller.cluster.datastore.exceptions.NotInitializedEx import org.opendaylight.controller.cluster.datastore.exceptions.PrimaryNotFoundException; import org.opendaylight.controller.cluster.datastore.exceptions.TimeoutException; import org.opendaylight.controller.cluster.datastore.messages.BatchedModifications; -import org.opendaylight.controller.cluster.datastore.messages.BatchedModificationsReply; import org.opendaylight.controller.cluster.datastore.messages.CloseTransaction; import org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction; import org.opendaylight.controller.cluster.datastore.modification.DeleteModification; @@ -45,18 +47,19 @@ import org.opendaylight.controller.cluster.datastore.modification.MergeModificat import org.opendaylight.controller.cluster.datastore.modification.WriteModification; import org.opendaylight.controller.cluster.datastore.shardstrategy.DefaultShardStrategy; import org.opendaylight.controller.cluster.datastore.utils.DoNothingActor; +import org.opendaylight.controller.cluster.datastore.utils.NormalizedNodeAggregatorTest; import org.opendaylight.controller.md.cluster.datastore.model.CarsModel; +import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper; import org.opendaylight.controller.md.cluster.datastore.model.TestModel; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages.CreateTransactionReply; import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; -import scala.concurrent.Await; -import scala.concurrent.Future; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; import scala.concurrent.Promise; -import scala.concurrent.duration.Duration; @SuppressWarnings("resource") public class TransactionProxyTest extends AbstractTransactionProxyTest { @@ -305,29 +308,6 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { transactionProxy.exists(TestModel.TEST_PATH); } - private void verifyRecordingOperationFutures(List> futures, - Class... expResultTypes) throws Exception { - assertEquals("getRecordingOperationFutures size", expResultTypes.length, futures.size()); - - int i = 0; - for( Future future: futures) { - assertNotNull("Recording operation Future is null", future); - - Class expResultType = expResultTypes[i++]; - if(Throwable.class.isAssignableFrom(expResultType)) { - try { - Await.result(future, Duration.create(5, TimeUnit.SECONDS)); - fail("Expected exception from recording operation Future"); - } catch(Exception e) { - // Expected - } - } else { - assertEquals(String.format("Recording operation %d Future result type", i +1 ), expResultType, - Await.result(future, Duration.create(5, TimeUnit.SECONDS)).getClass()); - } - } - } - @Test public void testWrite() throws Exception { dataStoreContextBuilder.shardBatchedModificationCount(1); @@ -356,8 +336,7 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { doReturn(readSerializedDataReply(null)).when(mockActorContext).executeOperationAsync( eq(actorSelection(actorRef)), eqSerializedReadData()); - expectBatchedModifications(actorRef, 1); - expectReadyTransaction(actorRef); + expectBatchedModificationsReady(actorRef); final NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); @@ -396,10 +375,7 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { // This sends the batched modification. transactionProxy.ready(); - verifyOneBatchedModification(actorRef, new WriteModification(TestModel.TEST_PATH, nodeToWrite), false); - - verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), - BatchedModificationsReply.class); + verifyOneBatchedModification(actorRef, new WriteModification(TestModel.TEST_PATH, nodeToWrite), true); } @Test(expected=IllegalStateException.class) @@ -448,7 +424,7 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { } @Test - public void testReadyWithReadWrite() throws Exception { + public void testReadWrite() throws Exception { ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); @@ -457,7 +433,34 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { eq(actorSelection(actorRef)), eqSerializedReadData()); expectBatchedModifications(actorRef, 1); - expectReadyTransaction(actorRef); + + TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); + + transactionProxy.read(TestModel.TEST_PATH); + + transactionProxy.write(TestModel.TEST_PATH, nodeToWrite); + + transactionProxy.read(TestModel.TEST_PATH); + + transactionProxy.read(TestModel.TEST_PATH); + + List batchedModifications = captureBatchedModifications(actorRef); + assertEquals("Captured BatchedModifications count", 1, batchedModifications.size()); + + verifyBatchedModifications(batchedModifications.get(0), false, + new WriteModification(TestModel.TEST_PATH, nodeToWrite)); + } + + @Test + public void testReadyWithReadWrite() throws Exception { + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); + + NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + + doReturn(readSerializedDataReply(null)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedReadData()); + + expectBatchedModificationsReady(actorRef); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); @@ -471,31 +474,29 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { ThreePhaseCommitCohortProxy proxy = (ThreePhaseCommitCohortProxy) ready; - verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), - BatchedModificationsReply.class); - verifyCohortFutures(proxy, getSystem().actorSelection(actorRef.path())); - verify(mockActorContext).executeOperationAsync(eq(actorSelection(actorRef)), - isA(BatchedModifications.class)); + List batchedModifications = captureBatchedModifications(actorRef); + assertEquals("Captured BatchedModifications count", 1, batchedModifications.size()); - verify(mockActorContext).executeOperationAsync(eq(actorSelection(actorRef)), - isA(ReadyTransaction.SERIALIZABLE_CLASS)); + verifyBatchedModifications(batchedModifications.get(0), true, + new WriteModification(TestModel.TEST_PATH, nodeToWrite)); + + assertEquals("getTotalMessageCount", 1, batchedModifications.get(0).getTotalMessagesSent()); } @Test - public void testReadyWithWriteOnlyAndLastBatchPending() throws Exception { - dataStoreContextBuilder.writeOnlyTransactionOptimizationsEnabled(true); - - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); + public void testReadyWithNoModifications() throws Exception { + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); - NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + doReturn(readSerializedDataReply(null)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(actorRef)), eqSerializedReadData()); - expectBatchedModificationsReady(actorRef, 1); + expectBatchedModificationsReady(actorRef); - TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, WRITE_ONLY); + TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); - transactionProxy.write(TestModel.TEST_PATH, nodeToWrite); + transactionProxy.read(TestModel.TEST_PATH); DOMStoreThreePhaseCommitCohort ready = transactionProxy.ready(); @@ -503,28 +504,23 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { ThreePhaseCommitCohortProxy proxy = (ThreePhaseCommitCohortProxy) ready; - verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures()); - verifyCohortFutures(proxy, getSystem().actorSelection(actorRef.path())); List batchedModifications = captureBatchedModifications(actorRef); assertEquals("Captured BatchedModifications count", 1, batchedModifications.size()); - verifyBatchedModifications(batchedModifications.get(0), true, - new WriteModification(TestModel.TEST_PATH, nodeToWrite)); - - verify(mockActorContext, never()).executeOperationAsync(eq(actorSelection(actorRef)), - isA(ReadyTransaction.SERIALIZABLE_CLASS)); + verifyBatchedModifications(batchedModifications.get(0), true); } @Test - public void testReadyWithWriteOnlyAndLastBatchEmpty() throws Exception { - dataStoreContextBuilder.shardBatchedModificationCount(1).writeOnlyTransactionOptimizationsEnabled(true); + public void testReadyWithWriteOnlyAndLastBatchPending() throws Exception { + dataStoreContextBuilder.writeOnlyTransactionOptimizationsEnabled(true); + ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - expectBatchedModificationsReady(actorRef, 1); + expectBatchedModificationsReady(actorRef); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, WRITE_ONLY); @@ -536,34 +532,26 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { ThreePhaseCommitCohortProxy proxy = (ThreePhaseCommitCohortProxy) ready; - verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), - BatchedModificationsReply.class); - verifyCohortFutures(proxy, getSystem().actorSelection(actorRef.path())); List batchedModifications = captureBatchedModifications(actorRef); - assertEquals("Captured BatchedModifications count", 2, batchedModifications.size()); + assertEquals("Captured BatchedModifications count", 1, batchedModifications.size()); - verifyBatchedModifications(batchedModifications.get(0), false, + verifyBatchedModifications(batchedModifications.get(0), true, new WriteModification(TestModel.TEST_PATH, nodeToWrite)); - verifyBatchedModifications(batchedModifications.get(1), true); - verify(mockActorContext, never()).executeOperationAsync(eq(actorSelection(actorRef)), isA(ReadyTransaction.SERIALIZABLE_CLASS)); } @Test - public void testReadyWithRecordingOperationFailure() throws Exception { + public void testReadyWithWriteOnlyAndLastBatchEmpty() throws Exception { dataStoreContextBuilder.shardBatchedModificationCount(1).writeOnlyTransactionOptimizationsEnabled(true); - ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), WRITE_ONLY); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); - expectFailedBatchedModifications(actorRef); - - doReturn(false).when(mockActorContext).isPathLocal(actorRef.path().toString()); + expectBatchedModificationsReady(actorRef); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, WRITE_ONLY); @@ -575,9 +563,18 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { ThreePhaseCommitCohortProxy proxy = (ThreePhaseCommitCohortProxy) ready; - verifyCohortFutures(proxy, TestException.class); + verifyCohortFutures(proxy, getSystem().actorSelection(actorRef.path())); - verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), TestException.class); + List batchedModifications = captureBatchedModifications(actorRef); + assertEquals("Captured BatchedModifications count", 2, batchedModifications.size()); + + verifyBatchedModifications(batchedModifications.get(0), false, + new WriteModification(TestModel.TEST_PATH, nodeToWrite)); + + verifyBatchedModifications(batchedModifications.get(1), true); + + verify(mockActorContext, never()).executeOperationAsync(eq(actorSelection(actorRef)), + isA(ReadyTransaction.SERIALIZABLE_CLASS)); } @Test @@ -749,18 +746,13 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE); doReturn(true).when(mockActorContext).isPathLocal(anyString()); - doReturn(batchedModificationsReply(1)).when(mockActorContext).executeOperationAsync( - any(ActorSelection.class), isA(BatchedModifications.class)); + expectBatchedModificationsReady(actorRef); TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); NormalizedNode nodeToWrite = ImmutableNodes.containerNode(TestModel.TEST_QNAME); transactionProxy.write(TestModel.TEST_PATH, nodeToWrite); - // testing ready - doReturn(readyTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( - eq(actorSelection(actorRef)), isA(ReadyTransaction.class)); - DOMStoreThreePhaseCommitCohort ready = transactionProxy.ready(); assertTrue(ready instanceof ThreePhaseCommitCohortProxy); @@ -1188,8 +1180,6 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { expectBatchedModifications(actorRef, shardBatchedModificationCount); - expectReadyTransaction(actorRef); - YangInstanceIdentifier writePath1 = TestModel.TEST_PATH; NormalizedNode writeNode1 = ImmutableNodes.containerNode(TestModel.TEST_QNAME); @@ -1234,17 +1224,10 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { verifyBatchedModifications(batchedModifications.get(1), false, new MergeModification(mergePath1, mergeNode1), new MergeModification(mergePath2, mergeNode2), new WriteModification(writePath3, writeNode3)); - boolean optimizedWriteOnly = type == WRITE_ONLY && dataStoreContextBuilder.build().isWriteOnlyTransactionOptimizationsEnabled(); - verifyBatchedModifications(batchedModifications.get(2), optimizedWriteOnly, new MergeModification(mergePath3, mergeNode3), + verifyBatchedModifications(batchedModifications.get(2), true, new MergeModification(mergePath3, mergeNode3), new DeleteModification(deletePath2)); - if(optimizedWriteOnly) { - verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), - BatchedModificationsReply.class, BatchedModificationsReply.class); - } else { - verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), - BatchedModificationsReply.class, BatchedModificationsReply.class, BatchedModificationsReply.class); - } + assertEquals("getTotalMessageCount", 3, batchedModifications.get(2).getTotalMessagesSent()); } @Test @@ -1349,8 +1332,80 @@ public class TransactionProxyTest extends AbstractTransactionProxyTest { inOrder.verify(mockActorContext).executeOperationAsync( eq(actorSelection(actorRef)), eqSerializedDataExists()); + } + + @Test + public void testReadRoot() throws ReadFailedException, InterruptedException, ExecutionException, java.util.concurrent.TimeoutException { + + SchemaContext schemaContext = SchemaContextHelper.full(); + Configuration configuration = mock(Configuration.class); + doReturn(configuration).when(mockActorContext).getConfiguration(); + doReturn(schemaContext).when(mockActorContext).getSchemaContext(); + doReturn(Sets.newHashSet("test", "cars")).when(configuration).getAllShardNames(); + + NormalizedNode expectedNode1 = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + NormalizedNode expectedNode2 = ImmutableNodes.containerNode(CarsModel.CARS_QNAME); + + setUpReadData("test", NormalizedNodeAggregatorTest.getRootNode(expectedNode1, schemaContext)); + setUpReadData("cars", NormalizedNodeAggregatorTest.getRootNode(expectedNode2, schemaContext)); - verifyRecordingOperationFutures(transactionProxy.getRecordedOperationFutures(), - BatchedModificationsReply.class, BatchedModificationsReply.class, BatchedModificationsReply.class); + doReturn(memberName).when(mockActorContext).getCurrentMemberName(); + + doReturn(10).when(mockActorContext).getTransactionOutstandingOperationLimit(); + + doReturn(getSystem().dispatchers().defaultGlobalDispatcher()).when(mockActorContext).getClientDispatcher(); + + TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_ONLY); + + Optional> readOptional = transactionProxy.read( + YangInstanceIdentifier.builder().build()).get(5, TimeUnit.SECONDS); + + assertEquals("NormalizedNode isPresent", true, readOptional.isPresent()); + + NormalizedNode normalizedNode = readOptional.get(); + + assertTrue("Expect value to be a Collection", normalizedNode.getValue() instanceof Collection); + + Collection> collection = (Collection>) normalizedNode.getValue(); + + for(NormalizedNode node : collection){ + assertTrue("Expected " + node + " to be a ContainerNode", node instanceof ContainerNode); + } + + assertTrue("Child with QName = " + TestModel.TEST_QNAME + " not found", + NormalizedNodeAggregatorTest.findChildWithQName(collection, TestModel.TEST_QNAME) != null); + + assertEquals(expectedNode1, NormalizedNodeAggregatorTest.findChildWithQName(collection, TestModel.TEST_QNAME)); + + assertTrue("Child with QName = " + CarsModel.BASE_QNAME + " not found", + NormalizedNodeAggregatorTest.findChildWithQName(collection, CarsModel.BASE_QNAME) != null); + + assertEquals(expectedNode2, NormalizedNodeAggregatorTest.findChildWithQName(collection, CarsModel.BASE_QNAME)); + } + + + private void setUpReadData(String shardName, NormalizedNode expectedNode) { + ActorSystem actorSystem = getSystem(); + ActorRef shardActorRef = getSystem().actorOf(Props.create(DoNothingActor.class)); + + doReturn(getSystem().actorSelection(shardActorRef.path())). + when(mockActorContext).actorSelection(shardActorRef.path().toString()); + + doReturn(Futures.successful(getSystem().actorSelection(shardActorRef.path()))). + when(mockActorContext).findPrimaryShardAsync(eq(shardName)); + + doReturn(true).when(mockActorContext).isPathLocal(shardActorRef.path().toString()); + + ActorRef txActorRef = actorSystem.actorOf(Props.create(DoNothingActor.class)); + + doReturn(actorSystem.actorSelection(txActorRef.path())). + when(mockActorContext).actorSelection(txActorRef.path().toString()); + + doReturn(Futures.successful(createTransactionReply(txActorRef, DataStoreVersions.CURRENT_VERSION))).when(mockActorContext). + executeOperationAsync(eq(actorSystem.actorSelection(shardActorRef.path())), + eqCreateTransaction(memberName, TransactionType.READ_ONLY)); + + doReturn(readSerializedDataReply(expectedNode)).when(mockActorContext).executeOperationAsync( + eq(actorSelection(txActorRef)), eqSerializedReadData(YangInstanceIdentifier.builder().build())); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumShardTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumShardTest.java index cc860eafc7..9e1557ae3c 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumShardTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumShardTest.java @@ -11,7 +11,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.inOrder; -import static org.opendaylight.controller.cluster.datastore.DataStoreVersions.CURRENT_VERSION; +import static org.opendaylight.controller.cluster.datastore.DataStoreVersions.HELIUM_2_VERSION; import akka.actor.ActorRef; import akka.actor.PoisonPill; import akka.dispatch.Dispatchers; @@ -246,7 +246,7 @@ public class PreLithiumShardTest extends AbstractShardTest { // Simulate the ForwardedReadyTransaction message for the first Tx that would be sent // by the ShardTransaction. - shard.tell(new ForwardedReadyTransaction(transactionID1, CURRENT_VERSION, + shard.tell(new ForwardedReadyTransaction(transactionID1, HELIUM_2_VERSION, cohort1, modification1, true), getRef()); ReadyTransactionReply readyReply = ReadyTransactionReply.fromSerializable( expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS)); @@ -261,11 +261,11 @@ public class PreLithiumShardTest extends AbstractShardTest { // Send the ForwardedReadyTransaction for the next 2 Tx's. - shard.tell(new ForwardedReadyTransaction(transactionID2, CURRENT_VERSION, + shard.tell(new ForwardedReadyTransaction(transactionID2, HELIUM_2_VERSION, cohort2, modification2, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); - shard.tell(new ForwardedReadyTransaction(transactionID3, CURRENT_VERSION, + shard.tell(new ForwardedReadyTransaction(transactionID3, HELIUM_2_VERSION, cohort3, modification3, true), getRef()); expectMsgClass(duration, ReadyTransactionReply.SERIALIZABLE_CLASS); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumTransactionProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumTransactionProxyTest.java index 2980f83564..4cf8b67ddb 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumTransactionProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/compat/PreLithiumTransactionProxyTest.java @@ -33,6 +33,7 @@ import org.opendaylight.controller.cluster.datastore.messages.DeleteDataReply; import org.opendaylight.controller.cluster.datastore.messages.MergeData; import org.opendaylight.controller.cluster.datastore.messages.MergeDataReply; import org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction; +import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply; import org.opendaylight.controller.cluster.datastore.messages.WriteData; import org.opendaylight.controller.cluster.datastore.messages.WriteDataReply; import org.opendaylight.controller.md.cluster.datastore.model.TestModel; @@ -41,6 +42,7 @@ import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCoh import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import scala.concurrent.Future; /** * Unit tests for backwards compatibility with pre-Lithium versions. @@ -93,6 +95,10 @@ public class PreLithiumTransactionProxyTest extends AbstractTransactionProxyTest return argThat(matcher); } + private Future readySerializedTxReply(String path, short version) { + return Futures.successful(new ReadyTransactionReply(path, version).toSerializable()); + } + private ActorRef testCompatibilityWithHeliumVersion(short version) throws Exception { ActorRef actorRef = setupActorContextWithInitialCreateTransaction(getSystem(), READ_WRITE, version); @@ -110,7 +116,7 @@ public class PreLithiumTransactionProxyTest extends AbstractTransactionProxyTest doReturn(Futures.successful(new DeleteDataReply().toSerializable(version))).when(mockActorContext). executeOperationAsync(eq(actorSelection(actorRef)), eqLegacyDeleteData(TestModel.TEST_PATH)); - doReturn(readySerializedTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( + doReturn(readySerializedTxReply(actorRef.path().toString(), version)).when(mockActorContext).executeOperationAsync( eq(actorSelection(actorRef)), isA(ReadyTransaction.SERIALIZABLE_CLASS)); doReturn(actorRef.path().toString()).when(mockActorContext).resolvePath(eq(actorRef.path().toString()), @@ -170,7 +176,7 @@ public class PreLithiumTransactionProxyTest extends AbstractTransactionProxyTest doReturn(Futures.successful(new WriteDataReply().toSerializable(version))).when(mockActorContext). executeOperationAsync(eq(actorSelection(actorRef)), eqLegacyWriteData(testNode)); - doReturn(readySerializedTxReply(actorRef.path().toString())).when(mockActorContext).executeOperationAsync( + doReturn(readySerializedTxReply(actorRef.path().toString(), version)).when(mockActorContext).executeOperationAsync( eq(actorSelection(actorRef)), isA(ReadyTransaction.SERIALIZABLE_CLASS)); doReturn(actorRef.path().toString()).when(mockActorContext).resolvePath(eq(actorRef.path().toString()), diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModificationsTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModificationsTest.java index c4027ad2a5..1df8e9775b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModificationsTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/messages/BatchedModificationsTest.java @@ -46,6 +46,7 @@ public class BatchedModificationsTest { batched.addModification(new MergeModification(mergePath, mergeData)); batched.addModification(new DeleteModification(deletePath)); batched.setReady(true); + batched.setTotalMessagesSent(5); BatchedModifications clone = (BatchedModifications) SerializationUtils.clone( (Serializable) batched.toSerializable()); @@ -54,6 +55,7 @@ public class BatchedModificationsTest { assertEquals("getTransactionID", "tx1", clone.getTransactionID()); assertEquals("getTransactionChainID", "txChain", clone.getTransactionChainID()); assertEquals("isReady", true, clone.isReady()); + assertEquals("getTotalMessagesSent", 5, clone.getTotalMessagesSent()); assertEquals("getModifications size", 3, clone.getModifications().size()); @@ -91,11 +93,5 @@ public class BatchedModificationsTest { BatchedModificationsReply clone = (BatchedModificationsReply) SerializationUtils.clone( (Serializable) new BatchedModificationsReply(100).toSerializable()); assertEquals("getNumBatched", 100, clone.getNumBatched()); - assertEquals("getCohortPath", null, clone.getCohortPath()); - - clone = (BatchedModificationsReply) SerializationUtils.clone( - (Serializable) new BatchedModificationsReply(50, "cohort path").toSerializable()); - assertEquals("getNumBatched", 50, clone.getNumBatched()); - assertEquals("getCohortPath", "cohort path", clone.getCohortPath()); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransactionReplyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransactionReplyTest.java new file mode 100644 index 0000000000..db525eafbe --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/messages/ReadyTransactionReplyTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.datastore.messages; + +import static org.junit.Assert.assertEquals; +import java.io.Serializable; +import org.apache.commons.lang.SerializationUtils; +import org.junit.Test; +import org.opendaylight.controller.cluster.datastore.DataStoreVersions; +import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages; + +/** + * Unit tests for ReadyTransactionReply. + * + * @author Thomas Pantelis + */ +public class ReadyTransactionReplyTest { + + @Test + public void testSerialization() { + String cohortPath = "cohort path"; + ReadyTransactionReply expected = new ReadyTransactionReply(cohortPath); + + Object serialized = expected.toSerializable(); + assertEquals("Serialized type", ReadyTransactionReply.class, serialized.getClass()); + + ReadyTransactionReply actual = ReadyTransactionReply.fromSerializable(SerializationUtils.clone( + (Serializable) serialized)); + assertEquals("getVersion", DataStoreVersions.CURRENT_VERSION, actual.getVersion()); + assertEquals("getCohortPath", cohortPath, actual.getCohortPath()); + } + + @Test + public void testSerializationWithPreLithiumVersion() throws Exception { + String cohortPath = "cohort path"; + ReadyTransactionReply expected = new ReadyTransactionReply(cohortPath, DataStoreVersions.HELIUM_2_VERSION); + + Object serialized = expected.toSerializable(); + assertEquals("Serialized type", ShardTransactionMessages.ReadyTransactionReply.class, serialized.getClass()); + + ReadyTransactionReply actual = ReadyTransactionReply.fromSerializable(SerializationUtils.clone( + (Serializable) serialized)); + assertEquals("getVersion", DataStoreVersions.HELIUM_2_VERSION, actual.getVersion()); + assertEquals("getCohortPath", cohortPath, actual.getCohortPath()); + } +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MessageCollectorActor.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MessageCollectorActor.java index d62c9dbc28..18c2985635 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MessageCollectorActor.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/MessageCollectorActor.java @@ -37,7 +37,7 @@ public class MessageCollectorActor extends UntypedActor { @Override public void onReceive(Object message) throws Exception { if(message instanceof String){ if("messages".equals(message)){ - getSender().tell(new ArrayList(messages), getSelf()); + getSender().tell(new ArrayList<>(messages), getSelf()); } } else { messages.add(message); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/NormalizedNodeAggregatorTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/NormalizedNodeAggregatorTest.java new file mode 100644 index 0000000000..8c8631089c --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/NormalizedNodeAggregatorTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.cluster.datastore.utils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.CheckedFuture; +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import org.junit.Test; +import org.opendaylight.controller.md.cluster.datastore.model.CarsModel; +import org.opendaylight.controller.md.cluster.datastore.model.SchemaContextHelper; +import org.opendaylight.controller.md.cluster.datastore.model.TestModel; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; +import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class NormalizedNodeAggregatorTest { + + @Test + public void testAggregate() throws InterruptedException, ExecutionException, ReadFailedException, DataValidationFailedException { + SchemaContext schemaContext = SchemaContextHelper.full(); + NormalizedNode expectedNode1 = ImmutableNodes.containerNode(TestModel.TEST_QNAME); + NormalizedNode expectedNode2 = ImmutableNodes.containerNode(CarsModel.CARS_QNAME); + + Optional> optional = NormalizedNodeAggregator.aggregate(YangInstanceIdentifier.builder().build(), + Lists.newArrayList( + Optional.>of(getRootNode(expectedNode1, schemaContext)), + Optional.>of(getRootNode(expectedNode2, schemaContext))), + schemaContext); + + + NormalizedNode normalizedNode = optional.get(); + + assertTrue("Expect value to be a Collection", normalizedNode.getValue() instanceof Collection); + + Collection> collection = (Collection>) normalizedNode.getValue(); + + for(NormalizedNode node : collection){ + assertTrue("Expected " + node + " to be a ContainerNode", node instanceof ContainerNode); + } + + assertTrue("Child with QName = " + TestModel.TEST_QNAME + " not found", + findChildWithQName(collection, TestModel.TEST_QNAME) != null); + + assertEquals(expectedNode1, findChildWithQName(collection, TestModel.TEST_QNAME)); + + assertTrue("Child with QName = " + CarsModel.BASE_QNAME + " not found", + findChildWithQName(collection, CarsModel.BASE_QNAME) != null); + + assertEquals(expectedNode2, findChildWithQName(collection, CarsModel.BASE_QNAME)); + + } + + public static NormalizedNode getRootNode(NormalizedNode moduleNode, SchemaContext schemaContext) throws ReadFailedException, ExecutionException, InterruptedException { + InMemoryDOMDataStore store = new InMemoryDOMDataStore("test", Executors.newSingleThreadExecutor()); + store.onGlobalContextUpdated(schemaContext); + + DOMStoreWriteTransaction writeTransaction = store.newWriteOnlyTransaction(); + + writeTransaction.merge(YangInstanceIdentifier.builder().node(moduleNode.getNodeType()).build(), moduleNode); + + DOMStoreThreePhaseCommitCohort ready = writeTransaction.ready(); + + ready.canCommit().get(); + ready.preCommit().get(); + ready.commit().get(); + + DOMStoreReadTransaction readTransaction = store.newReadOnlyTransaction(); + + CheckedFuture>, ReadFailedException> read = readTransaction.read(YangInstanceIdentifier.builder().build()); + + Optional> nodeOptional = read.checkedGet(); + + return nodeOptional.get(); + } + + public static NormalizedNode findChildWithQName(Collection> collection, QName qName) { + for(NormalizedNode node : collection){ + if(node.getNodeType().equals(qName)){ + return node; + } + } + + return null; + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongFuture.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongFuture.java index 1dfc607e6f..611303a41a 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongFuture.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongFuture.java @@ -7,7 +7,6 @@ */ package org.opendaylight.controller.md.sal.dom.broker.impl; -import com.google.common.base.Preconditions; import com.google.common.util.concurrent.AbstractCheckedFuture; import com.google.common.util.concurrent.ListenableFuture; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; @@ -16,13 +15,17 @@ import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFaile * A {@link Future} used to report the status of an future {@link java.util.concurrent.Future}. */ final class PingPongFuture extends AbstractCheckedFuture { - protected PingPongFuture(final ListenableFuture delegate) { - super(delegate); - } + protected PingPongFuture(final ListenableFuture delegate) { + super(delegate); + } - @Override - protected TransactionCommitFailedException mapException(final Exception e) { - Preconditions.checkArgument(e instanceof TransactionCommitFailedException); - return (TransactionCommitFailedException) e; + @Override + protected TransactionCommitFailedException mapException(final Exception e) { + if (e.getCause() instanceof TransactionCommitFailedException){ + return (TransactionCommitFailedException) e.getCause(); + } else { + return new TransactionCommitFailedException(e.getMessage(), e.getCause(), null); } + } } + diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml b/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml index a8c05a166d..4a17273232 100644 --- a/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml @@ -58,7 +58,6 @@ org.opendaylight.controller sal-akka-raft - 1.2.0-SNAPSHOT diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java index e06f572cad..303f3e6923 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java @@ -31,6 +31,7 @@ import javax.xml.transform.dom.DOMResult; import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult; import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.util.OrderedNormalizedNodeWriter; import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -44,7 +45,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; -import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; @@ -206,17 +206,15 @@ public class NetconfMessageTransformer implements MessageTransformer editElement : normalized.getValue()) { - normalizedNodeWriter.write(editElement); - } + normalizedNodeWriter = new OrderedNormalizedNodeWriter(normalizedNodeStreamWriter, baseNetconfCtx, schemaPath); + Collection> value = (Collection) normalized.getValue(); + normalizedNodeWriter.write(value); normalizedNodeWriter.flush(); } finally { try { diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java index e1ffcdddff..47bdcd8cc8 100644 --- a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java @@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import io.netty.channel.Channel; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -94,12 +95,15 @@ import org.opendaylight.controller.netconf.confignetconfconnector.operations.run import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreContext; import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreService; import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; +import org.opendaylight.controller.netconf.impl.NetconfServerSession; +import org.opendaylight.controller.netconf.impl.NetconfServerSessionListener; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCloseSession; import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -387,7 +391,14 @@ public class NetconfMappingTest extends AbstractConfigTest { private void closeSession() throws NetconfDocumentedException, ParserConfigurationException, SAXException, IOException { + final Channel channel = mock(Channel.class); + doReturn("channel").when(channel).toString(); + final NetconfServerSessionListener listener = mock(NetconfServerSessionListener.class); + final NetconfServerSession session = + new NetconfServerSession(listener, channel, 1L, + NetconfHelloMessageAdditionalHeader.fromString("[netconf;10.12.0.102:48528;ssh;;;;;;]")); DefaultCloseSession closeOp = new DefaultCloseSession(NETCONF_SESSION_ID, sessionCloseable); + closeOp.setNetconfSession(session); executeOp(closeOp, "netconfMessages/closeSession.xml"); } diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/RuntimeRpc.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/RuntimeRpc.java index a3cd3c7afa..51f9e220c5 100644 --- a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/RuntimeRpc.java +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/RuntimeRpc.java @@ -14,6 +14,7 @@ import com.google.common.util.concurrent.CheckedFuture; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.Collection; import java.util.Collections; import java.util.Map; import javax.annotation.Nullable; @@ -32,16 +33,15 @@ import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext; +import org.opendaylight.controller.netconf.util.OrderedNormalizedNodeWriter; import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException; import org.opendaylight.controller.netconf.util.mapping.AbstractSingletonNetconfOperation; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlUtil; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; -import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils; import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory; @@ -211,7 +211,7 @@ public class RuntimeRpc extends AbstractSingletonNetconfOperation { final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, schemaContext.getCurrentContext(), rpcOutputPath); - final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter); + final OrderedNormalizedNodeWriter nnWriter = new OrderedNormalizedNodeWriter(nnStreamWriter, schemaContext.getCurrentContext(), rpcOutputPath); writeRootElement(xmlWriter, nnWriter, (ContainerNode) data); try { @@ -232,11 +232,10 @@ public class RuntimeRpc extends AbstractSingletonNetconfOperation { } } - private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) { + private void writeRootElement(final XMLStreamWriter xmlWriter, final OrderedNormalizedNodeWriter nnWriter, final ContainerNode data) { try { - for (final DataContainerChild child : data.getValue()) { - nnWriter.write(child); - } + Collection> value = (Collection) data.getValue(); + nnWriter.write(value); nnWriter.flush(); xmlWriter.flush(); } catch (XMLStreamException | IOException e) { diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/controller/netconf/mdsal/connector/ops/RuntimeRpcTest.java b/opendaylight/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/controller/netconf/mdsal/connector/ops/RuntimeRpcTest.java index 32eb08c2e7..040066d91c 100644 --- a/opendaylight/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/controller/netconf/mdsal/connector/ops/RuntimeRpcTest.java +++ b/opendaylight/netconf/mdsal-netconf-connector/src/test/java/org/opendaylight/controller/netconf/mdsal/connector/ops/RuntimeRpcTest.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.xml.transform.TransformerException; import org.custommonkey.xmlunit.DetailedDiff; import org.custommonkey.xmlunit.Diff; import org.custommonkey.xmlunit.XMLUnit; @@ -201,6 +202,18 @@ public class RuntimeRpcTest { verifyResponse(response, XmlFileLoader.xmlFileToDocument("messages/mapping/rpcs/rpc-nonvoid-control.xml")); } + @Test + public void testSuccesfullContainerInvocation() throws Exception { + RuntimeRpc rpc = new RuntimeRpc(sessionIdForReporting, currentSchemaContext, rpcServiceSuccesfullInvocation); + + Document rpcDocument = XmlFileLoader.xmlFileToDocument("messages/mapping/rpcs/rpc-container.xml"); + HandlingPriority priority = rpc.canHandle(rpcDocument); + Preconditions.checkState(priority != HandlingPriority.CANNOT_HANDLE); + + Document response = rpc.handle(rpcDocument, NetconfOperationChainedExecution.EXECUTION_TERMINATION_POINT); + verifyResponse(response, XmlFileLoader.xmlFileToDocument("messages/mapping/rpcs/rpc-container-control.xml")); + } + @Test public void testFailedInvocation() throws Exception { RuntimeRpc rpc = new RuntimeRpc(sessionIdForReporting, currentSchemaContext, rpcServiceFailedInvocation); @@ -232,10 +245,11 @@ public class RuntimeRpcTest { verifyResponse(response, RPC_REPLY_OK); } - private void verifyResponse(Document response, Document template) { + private void verifyResponse(Document response, Document template) throws IOException, TransformerException { DetailedDiff dd = new DetailedDiff(new Diff(response, template)); dd.overrideElementQualifier(new RecursiveElementNameAndTextQualifier()); - assertTrue(dd.similar()); + //we care about order so response has to be identical + assertTrue(dd.identical()); } private RpcDefinition getRpcDefinitionFromModule(Module module, URI namespaceURI, String name) { diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/rpcs/rpc-container-control.xml b/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/rpcs/rpc-container-control.xml new file mode 100644 index 0000000000..1c06ea95ff --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/rpcs/rpc-container-control.xml @@ -0,0 +1,27 @@ + + + + + + cont1 input string 1 + + + cont1 input string 2 + + + + + cont2 input string 1 + + + cont2 input string 2 + + + \ No newline at end of file diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/rpcs/rpc-container.xml b/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/rpcs/rpc-container.xml new file mode 100644 index 0000000000..570d6df2f0 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/messages/mapping/rpcs/rpc-container.xml @@ -0,0 +1,29 @@ + + + + + + + cont1 input string 1 + + + cont1 input string 2 + + + + + cont2 input string 1 + + + cont2 input string 2 + + + + \ No newline at end of file diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-rpc-test.yang b/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-rpc-test.yang index d493840828..6a59cdcf6e 100644 --- a/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-rpc-test.yang +++ b/opendaylight/netconf/mdsal-netconf-connector/src/test/resources/yang/mdsal-netconf-rpc-test.yang @@ -40,5 +40,51 @@ module rpc-test { } } } + + rpc container-rpc { + input { + container cont1 { + leaf test-string { + type string; + } + + leaf test-string2 { + type string; + } + } + + container cont2 { + leaf test-string { + type string; + } + + leaf test-string2 { + type string; + } + } + } + + output { + container cont1 { + leaf test-string { + type string; + } + + leaf test-string2 { + type string; + } + } + + container cont2 { + leaf test-string { + type string; + } + + leaf test-string2 { + type string; + } + } + } + } } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java index 0cf2dbc281..4368f7ec79 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java @@ -10,6 +10,8 @@ package org.opendaylight.controller.netconf.impl; import com.google.common.base.Preconditions; import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToByteEncoder; import java.text.SimpleDateFormat; @@ -45,6 +47,7 @@ public final class NetconfServerSession extends AbstractNetconfSessionabsent()); } + + @Override + public void setNetconfSession(final NetconfServerSession s) { + this.session = s; + } } diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCloseSessionTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCloseSessionTest.java index d82c1484e4..b45b116b12 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCloseSessionTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCloseSessionTest.java @@ -8,25 +8,67 @@ package org.opendaylight.controller.netconf.impl.mapping.operations; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.util.concurrent.GenericFutureListener; import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.NetconfTerminationReason; +import org.opendaylight.controller.netconf.impl.NetconfServerSession; +import org.opendaylight.controller.netconf.impl.NetconfServerSessionListener; +import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.w3c.dom.Document; public class DefaultCloseSessionTest { + @Test public void testDefaultCloseSession() throws Exception { AutoCloseable res = mock(AutoCloseable.class); doNothing().when(res).close(); - DefaultCloseSession session = new DefaultCloseSession("", res); + DefaultCloseSession close = new DefaultCloseSession("", res); Document doc = XmlUtil.newDocument(); XmlElement elem = XmlElement.fromDomElement(XmlUtil.readXmlToElement("")); - session.handleWithNoSubsequentOperations(doc, elem); + final Channel channel = mock(Channel.class); + doReturn("channel").when(channel).toString(); + doReturn(mock(ChannelFuture.class)).when(channel).close(); + + final ChannelFuture sendFuture = mock(ChannelFuture.class); + doAnswer(new Answer() { + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable { + ((GenericFutureListener) invocation.getArguments()[0]).operationComplete(sendFuture); + return null; + } + }).when(sendFuture).addListener(any(GenericFutureListener.class)); + doReturn(sendFuture).when(channel).writeAndFlush(anyObject()); + final NetconfServerSessionListener listener = mock(NetconfServerSessionListener.class); + doNothing().when(listener).onSessionTerminated(any(NetconfServerSession.class), any(NetconfTerminationReason.class)); + final NetconfServerSession session = + new NetconfServerSession(listener, channel, 1L, + NetconfHelloMessageAdditionalHeader.fromString("[netconf;10.12.0.102:48528;ssh;;;;;;]")); + close.setNetconfSession(session); + close.handleWithNoSubsequentOperations(doc, elem); + // Fake close response to trigger delayed close + session.sendMessage(new NetconfMessage(XmlUtil.readXmlToDocument("\n" + + "\n" + + ""))); + verify(channel).close(); + verify(listener).onSessionTerminated(any(NetconfServerSession.class), any(NetconfTerminationReason.class)); } @Test(expected = NetconfDocumentedException.class) diff --git a/opendaylight/netconf/netconf-util/pom.xml b/opendaylight/netconf/netconf-util/pom.xml index 6f9ebede8d..5d82cf1ccd 100644 --- a/opendaylight/netconf/netconf-util/pom.xml +++ b/opendaylight/netconf/netconf-util/pom.xml @@ -54,6 +54,10 @@ org.opendaylight.yangtools yang-model-api + + org.opendaylight.yangtools + yang-data-api + diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/OrderedNormalizedNodeWriter.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/OrderedNormalizedNodeWriter.java new file mode 100644 index 0000000000..e5d63b0fab --- /dev/null +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/OrderedNormalizedNodeWriter.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.util; + +import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Iterables; +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +//TODO this does not extend NormalizedNodeWriter from yangtools due to api freeze, make this inherit common methods to avoid code duplication +//TODO move this to yangtools, since this is in netconf-util due to api freeze in lithium +public class OrderedNormalizedNodeWriter implements Closeable, Flushable{ + + private final SchemaContext schemaContext; + private final SchemaNode root; + private final NormalizedNodeStreamWriter writer; + + public OrderedNormalizedNodeWriter(NormalizedNodeStreamWriter writer, SchemaContext schemaContext, SchemaPath path) { + this.writer = writer; + this.schemaContext = schemaContext; + this.root = findParentSchemaOnPath(schemaContext, path); + } + + public OrderedNormalizedNodeWriter write(final NormalizedNode node) throws IOException { + if (root == schemaContext) { + return write(node, schemaContext.getDataChildByName(node.getNodeType())); + } + + return write(node, root); + } + + public OrderedNormalizedNodeWriter write(final Collection> nodes) throws IOException { + if (writeChildren(nodes, root, false)) { + return this; + } + + throw new IllegalStateException("It wasn't possible to serialize nodes " + nodes); + + } + + private OrderedNormalizedNodeWriter write(NormalizedNode node, SchemaNode dataSchemaNode) throws IOException { + if (node == null) { + return this; + } + + if (wasProcessedAsCompositeNode(node, dataSchemaNode)) { + return this; + } + + if (wasProcessAsSimpleNode(node)) { + return this; + } + + throw new IllegalStateException("It wasn't possible to serialize node " + node); + } + + private void write(List> nodes, SchemaNode dataSchemaNode) throws IOException { + for (NormalizedNode node : nodes) { + write(node, dataSchemaNode); + } + } + + private OrderedNormalizedNodeWriter writeLeaf(final NormalizedNode node) throws IOException { + if (wasProcessAsSimpleNode(node)) { + return this; + } + + throw new IllegalStateException("It wasn't possible to serialize node " + node); + } + + private boolean writeChildren(final Iterable> children, SchemaNode parentSchemaNode, boolean endParent) throws IOException { + //Augmentations cannot be gotten with node.getChild so create our own structure with augmentations resolved + ArrayListMultimap> qNameToNodes = ArrayListMultimap.create(); + for (NormalizedNode child : children) { + if (child instanceof AugmentationNode) { + qNameToNodes.putAll(resolveAugmentations(child)); + } else { + qNameToNodes.put(child.getNodeType(), child); + } + } + + if (parentSchemaNode instanceof DataNodeContainer) { + if (parentSchemaNode instanceof ListSchemaNode && qNameToNodes.containsKey(parentSchemaNode.getQName())) { + write(qNameToNodes.get(parentSchemaNode.getQName()), parentSchemaNode); + } else { + for (DataSchemaNode schemaNode : ((DataNodeContainer) parentSchemaNode).getChildNodes()) { + write(qNameToNodes.get(schemaNode.getQName()), schemaNode); + } + } + } else if(parentSchemaNode instanceof ChoiceSchemaNode) { + for (ChoiceCaseNode ccNode : ((ChoiceSchemaNode) parentSchemaNode).getCases()) { + for (DataSchemaNode dsn : ccNode.getChildNodes()) { + if (qNameToNodes.containsKey(dsn.getQName())) { + write(qNameToNodes.get(dsn.getQName()), dsn); + } + } + } + } else { + for (NormalizedNode child : children) { + writeLeaf(child); + } + } + if (endParent) { + writer.endNode(); + } + return true; + } + + private ArrayListMultimap> resolveAugmentations(NormalizedNode child) { + final ArrayListMultimap> resolvedAugs = ArrayListMultimap.create(); + for (NormalizedNode node : ((AugmentationNode) child).getValue()) { + if (node instanceof AugmentationNode) { + resolvedAugs.putAll(resolveAugmentations(node)); + } else { + resolvedAugs.put(node.getNodeType(), node); + } + } + return resolvedAugs; + } + + private boolean writeMapEntryNode(final MapEntryNode node, final SchemaNode dataSchemaNode) throws IOException { + if(writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer) + .startMapEntryNode(node.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(node.getValue()), node.getAttributes()); + } else { + writer.startMapEntryNode(node.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(node.getValue())); + } + return writeChildren(node.getValue(), dataSchemaNode, true); + } + + private boolean wasProcessAsSimpleNode(final NormalizedNode node) throws IOException { + if (node instanceof LeafSetEntryNode) { + final LeafSetEntryNode nodeAsLeafList = (LeafSetEntryNode)node; + if(writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer).leafSetEntryNode(nodeAsLeafList.getValue(), nodeAsLeafList.getAttributes()); + } else { + writer.leafSetEntryNode(nodeAsLeafList.getValue()); + } + return true; + } else if (node instanceof LeafNode) { + final LeafNode nodeAsLeaf = (LeafNode)node; + if(writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer).leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue(), nodeAsLeaf.getAttributes()); + } else { + writer.leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue()); + } + return true; + } else if (node instanceof AnyXmlNode) { + final AnyXmlNode anyXmlNode = (AnyXmlNode)node; + writer.anyxmlNode(anyXmlNode.getIdentifier(), anyXmlNode.getValue()); + return true; + } + + return false; + } + + private boolean wasProcessedAsCompositeNode(final NormalizedNode node, SchemaNode dataSchemaNode) throws IOException { + if (node instanceof ContainerNode) { + final ContainerNode n = (ContainerNode) node; + if(writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer).startContainerNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue()), n.getAttributes()); + } else { + writer.startContainerNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue())); + } + return writeChildren(n.getValue(), dataSchemaNode, true); + } + if (node instanceof MapEntryNode) { + return writeMapEntryNode((MapEntryNode) node, dataSchemaNode); + } + if (node instanceof UnkeyedListEntryNode) { + final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node; + writer.startUnkeyedListItem(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue())); + return writeChildren(n.getValue(), dataSchemaNode, true); + } + if (node instanceof ChoiceNode) { + final ChoiceNode n = (ChoiceNode) node; + writer.startChoiceNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue())); + return writeChildren(n.getValue(), dataSchemaNode, true); + } + if (node instanceof AugmentationNode) { + final AugmentationNode n = (AugmentationNode) node; + writer.startAugmentationNode(n.getIdentifier()); + return writeChildren(n.getValue(), dataSchemaNode, true); + } + if (node instanceof UnkeyedListNode) { + final UnkeyedListNode n = (UnkeyedListNode) node; + writer.startUnkeyedList(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue())); + return writeChildren(n.getValue(), dataSchemaNode, true); + } + if (node instanceof OrderedMapNode) { + final OrderedMapNode n = (OrderedMapNode) node; + writer.startOrderedMapNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue())); + return writeChildren(n.getValue(), dataSchemaNode, true); + } + if (node instanceof MapNode) { + final MapNode n = (MapNode) node; + writer.startMapNode(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue())); + return writeChildren(n.getValue(), dataSchemaNode, true); + } + if (node instanceof LeafSetNode) { + //covers also OrderedLeafSetNode for which doesn't exist start* method + final LeafSetNode n = (LeafSetNode) node; + writer.startLeafSet(n.getIdentifier(), OrderedNormalizedNodeWriter.childSizeHint(n.getValue())); + return writeChildren(n.getValue(), dataSchemaNode, true); + } + + return false; + } + + private static final int childSizeHint(final Iterable children) { + return (children instanceof Collection) ? ((Collection) children).size() : UNKNOWN_SIZE; + } + + //TODO similar code is already present in schemaTracker, unify this when this writer is moved back to yangtools + private SchemaNode findParentSchemaOnPath(SchemaContext schemaContext, SchemaPath path) { + SchemaNode current = Preconditions.checkNotNull(schemaContext); + for (final QName qname : path.getPathFromRoot()) { + SchemaNode child; + if(current instanceof DataNodeContainer) { + child = ((DataNodeContainer) current).getDataChildByName(qname); + + if (child == null && current instanceof SchemaContext) { + child = tryFindGroupings((SchemaContext) current, qname).orNull(); + } + + if(child == null && current instanceof SchemaContext) { + child = tryFindNotification((SchemaContext) current, qname) + .or(tryFindRpc(((SchemaContext) current), qname)).orNull(); + } + } else if (current instanceof ChoiceSchemaNode) { + child = ((ChoiceSchemaNode) current).getCaseNodeByName(qname); + } else if (current instanceof RpcDefinition) { + switch (qname.getLocalName()) { + case "input": + child = ((RpcDefinition) current).getInput(); + break; + case "output": + child = ((RpcDefinition) current).getOutput(); + break; + default: + child = null; + break; + } + } else { + throw new IllegalArgumentException(String.format("Schema node %s does not allow children.", current)); + } + current = child; + } + return current; + } + + //TODO this method is already present in schemaTracker, unify this when this writer is moved back to yangtools + private Optional tryFindGroupings(final SchemaContext ctx, final QName qname) { + return Optional. fromNullable(Iterables.find(ctx.getGroupings(), new SchemaNodePredicate(qname), null)); + } + + //TODO this method is already present in schemaTracker, unify this when this writer is moved back to yangtools + private Optional tryFindRpc(final SchemaContext ctx, final QName qname) { + return Optional.fromNullable(Iterables.find(ctx.getOperations(), new SchemaNodePredicate(qname), null)); + } + + //TODO this method is already present in schemaTracker, unify this when this writer is moved back to yangtools + private Optional tryFindNotification(final SchemaContext ctx, final QName qname) { + return Optional.fromNullable(Iterables.find(ctx.getNotifications(), new SchemaNodePredicate(qname), null)); + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + writer.flush(); + writer.close(); + } + + //TODO this class is already present in schemaTracker, unify this when this writer is moved back to yangtools + private static final class SchemaNodePredicate implements Predicate { + private final QName qname; + + public SchemaNodePredicate(final QName qname) { + this.qname = qname; + } + + @Override + public boolean apply(final SchemaNode input) { + return input.getQName().equals(qname); + } + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java index e17cad977c..4529f81e57 100644 --- a/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java +++ b/opendaylight/netconf/netconf-util/src/main/java/org/opendaylight/controller/netconf/util/xml/XmlElement.java @@ -237,7 +237,7 @@ public final class XmlElement { /** * * @param tagName tag name without prefix - * @return + * @return List of child elements */ public List getChildElements(final String tagName) { return getChildElementsInternal(new ElementFilteringStrategy() { @@ -261,31 +261,47 @@ public final class XmlElement { } public Optional getOnlyChildElementOptionally(String childName) { - try { - return Optional.of(getOnlyChildElement(childName)); - } catch (Exception e) { + List nameElements = getChildElements(childName); + if (nameElements.size() != 1) { return Optional.absent(); } + return Optional.of(nameElements.get(0)); } - public Optional getOnlyChildElementOptionally(String childName, String namespace) { - try { - return Optional.of(getOnlyChildElement(childName, namespace)); - } catch (Exception e) { + public Optional getOnlyChildElementOptionally(final String childName, final String namespace) { + List children = getChildElementsWithinNamespace(namespace); + children = Lists.newArrayList(Collections2.filter(children, new Predicate() { + @Override + public boolean apply(XmlElement xmlElement) { + return xmlElement.getName().equals(childName); + } + })); + if (children.size() != 1){ return Optional.absent(); } + return Optional.of(children.get(0)); } public XmlElement getOnlyChildElementWithSameNamespace(String childName) throws NetconfDocumentedException { return getOnlyChildElement(childName, getNamespace()); } - public Optional getOnlyChildElementWithSameNamespaceOptionally(String childName) { - try { - return Optional.of(getOnlyChildElement(childName, getNamespace())); - } catch (Exception e) { - return Optional.absent(); + public Optional getOnlyChildElementWithSameNamespaceOptionally(final String childName) { + Optional namespace = getNamespaceOptionally(); + if (namespace.isPresent()) { + List children = getChildElementsWithinNamespace(namespace.get()); + children = Lists.newArrayList(Collections2.filter(children, new Predicate() { + @Override + public boolean apply(XmlElement xmlElement) { + return xmlElement.getName().equals(childName); + } + })); + if (children.size() != 1){ + return Optional.absent(); + } + return Optional.of(children.get(0)); } + return Optional.absent(); } public XmlElement getOnlyChildElementWithSameNamespace() throws NetconfDocumentedException { @@ -295,13 +311,14 @@ public final class XmlElement { } public Optional getOnlyChildElementWithSameNamespaceOptionally() { - try { - XmlElement childElement = getOnlyChildElement(); - childElement.checkNamespace(getNamespace()); - return Optional.of(childElement); - } catch (Exception e) { - return Optional.absent(); + Optional child = getOnlyChildElementOptionally(); + if (child.isPresent() + && child.get().getNamespaceOptionally().isPresent() + && getNamespaceOptionally().isPresent() + && getNamespaceOptionally().get().equals(child.get().getNamespaceOptionally().get())) { + return child; } + return Optional.absent(); } public XmlElement getOnlyChildElement(final String childName, String namespace) throws NetconfDocumentedException { @@ -335,6 +352,14 @@ public final class XmlElement { return children.get(0); } + public Optional getOnlyChildElementOptionally() { + List children = getChildElements(); + if (children.size() != 1) { + return Optional.absent(); + } + return Optional.of(children.get(0)); + } + public String getTextContent() throws NetconfDocumentedException { NodeList childNodes = element.getChildNodes(); if (childNodes.getLength() == 0) { @@ -377,6 +402,14 @@ public final class XmlElement { return attribute; } + public Optional getNamespaceAttributeOptionally(){ + String attribute = element.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY); + if (attribute == null || attribute.equals(DEFAULT_NAMESPACE_PREFIX)){ + return Optional.absent(); + } + return Optional.of(attribute); + } + public Optional getNamespaceOptionally() { String namespaceURI = element.getNamespaceURI(); if (Strings.isNullOrEmpty(namespaceURI)) { @@ -388,7 +421,7 @@ public final class XmlElement { public String getNamespace() throws MissingNameSpaceException { Optional namespaceURI = getNamespaceOptionally(); - if (namespaceURI.isPresent() == false){ + if (!namespaceURI.isPresent()){ throw new MissingNameSpaceException(String.format("No namespace defined for %s", this), NetconfDocumentedException.ErrorType.application, NetconfDocumentedException.ErrorTag.operation_failed, @@ -482,11 +515,8 @@ public final class XmlElement { XmlElement that = (XmlElement) o; - if (!element.isEqualNode(that.element)) { - return false; - } + return element.isEqualNode(that.element); - return true; } @Override @@ -495,15 +525,10 @@ public final class XmlElement { } public boolean hasNamespace() { - try { - getNamespaceAttribute(); - } catch (MissingNameSpaceException e) { - try { - getNamespace(); - } catch (MissingNameSpaceException e1) { + if (!getNamespaceAttributeOptionally().isPresent()) { + if (!getNamespaceOptionally().isPresent()) { return false; } - return true; } return true; }