From: Ed Warnicke Date: Fri, 4 Jul 2014 20:21:47 +0000 (+0000) Subject: Merge "Distributed Datastore integration with config subsystem Updated with the usage... X-Git-Tag: release/helium~535 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=d5c00a94aa86ca1ef0a42c096f2b8d41941ace85;hp=eb095e9201b6279355aac5a1ba3252dbd8564358 Merge "Distributed Datastore integration with config subsystem Updated with the usage of sal-dom-spi exposed Config Datastore and Operation Datastore services" --- diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index de8caa0e67..3aeb2ccba7 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -43,7 +43,6 @@ 7.0.32.v201211201952 7.0.32.v201211201952 0.0.3-SNAPSHOT - 1.7 7.0.32.v201211201952 7.0.32.v201211081135 1.2.2 diff --git a/opendaylight/distribution/opendaylight/src/main/resources/functions.sh b/opendaylight/distribution/opendaylight/src/main/resources/functions.sh new file mode 100644 index 0000000000..21dd4c16ba --- /dev/null +++ b/opendaylight/distribution/opendaylight/src/main/resources/functions.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Function harvestHelp searches in run.sh part for line starting with "##". +# Next lines starting with "#" will be printed without first char # (=help content). +# Help content has to end with "##" on new line. +# Example: +##foo +# Foo is world wide used synnonym for bar. +## +function harvestHelp() { + key="$1" + if [ -z "${key}" ]; then + key='HELP' + fi + echo + sed -rn "/^##${key}$/,/^##/ p" $0 | sed -r '1 d; $ d; s/^#/ /' + grep "##${key}" $0 > /dev/null +} diff --git a/opendaylight/distribution/opendaylight/src/main/resources/run.bat b/opendaylight/distribution/opendaylight/src/main/resources/run.bat index 72ccd02707..ce13e33968 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/run.bat +++ b/opendaylight/distribution/opendaylight/src/main/resources/run.bat @@ -14,6 +14,7 @@ SET jvmMaxMemory= SET extraJVMOpts= SET consoleOpts=-console -consoleLog SET PID= +SET JAVA_H=%JAVA_HOME%\bin\jps.exe :LOOP IF "%~1" NEQ "" ( @@ -61,7 +62,7 @@ IF "%~1" NEQ "" ( GOTO :LOOP ) IF "!CARG!"=="-status" ( - for /F "TOKENS=1" %%G in ('%JAVA_HOME%\bin\jps.exe -lvV ^| find /I "opendaylight"') do ( + for /F "TOKENS=1" %%G in ('""!JAVA_H!" -lvV ^| find /I "opendaylight""') do ( set PID=%%G ) if "!PID!" NEQ "" ( @@ -72,7 +73,7 @@ IF "%~1" NEQ "" ( GOTO :EOF ) IF "!CARG!"=="-stop" ( - for /F "TOKENS=1" %%G in ('%JAVA_HOME%\bin\jps.exe -lvV ^| find /I "opendaylight"') do ( + for /F "TOKENS=1" %%G in ('""!JAVA_H!" -lvV ^| find /I "opendaylight""') do ( set PID=%%G ) if "!PID!" NEQ "" ( @@ -89,18 +90,23 @@ IF "%~1" NEQ "" ( GOTO :LOOP ) IF "!CARG:~0,2!"=="-D" ( - SET extraJVMOpts=%extraJVMOpts% !CARG! + SET extraJVMOpts=!extraJVMOpts! !CARG! SHIFT GOTO :LOOP ) IF "!CARG:~0,2!"=="-X" ( - SET extraJVMOpts=%extraJVMOpts% !CARG! + SET extraJVMOpts=!extraJVMOpts! !CARG! SHIFT GOTO :LOOP ) IF "!CARG!"=="-help" ( - ECHO "Usage: %0 [-jmx] [-jmxport ] [-debug] [-debugsuspend] [-debugport ] [-start] [-consoleport ]] [-stop] [-status] [-console] [-help] []" - ECHO Note: Enclose any JVM or System properties within double quotes. + SHIFT + SET CARG=%2 + IF "!CARG!" NEQ "" ( + CALL:!CARG! + ) ELSE ( + CALL:helper + ) GOTO :EOF ) @@ -110,28 +116,24 @@ IF "%~1" NEQ "" ( IF "%debugEnabled%" NEQ "" ( REM ECHO "DEBUG enabled" - SET extraJVMOpts=%extraJVMOpts% -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=%debugport% + SET extraJVMOpts=!extraJVMOpts! -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=%debugport% ) + IF "%debugSuspended%" NEQ "" ( REM ECHO "DEBUG enabled suspended" - SET extraJVMOpts=%extraJVMOpts% -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%debugport% + SET extraJVMOpts=!extraJVMOpts! -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%debugport% ) IF "%jvmMaxMemory%"=="" ( SET jvmMaxMemory=-Xmx1G - ECHO ***************************************************************** - ECHO JVM maximum memory was not defined. Setting maximum memory to 1G. - ECHO To define the maximum memory, specify the -Xmx setting on the - ECHO command line. - ECHO e.g. run.bat -Xmx1G - ECHO *****************************************************************" + ECHO Setting maximum memory to 1G. ) -SET extraJVMOpts=%extraJVMOpts% %jvmMaxMemory% +SET extraJVMOpts=!extraJVMOpts! %jvmMaxMemory% IF "%jmxEnabled%" NEQ "" ( REM ECHO "JMX enabled " - SET extraJVMOpts=%extraJVMOpts% -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=%jmxport% -Dcom.sun.management.jmxremote + SET extraJVMOpts=!extraJVMOpts! -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=%jmxport% -Dcom.sun.management.jmxremote ) IF "%startEnabled%" NEQ "" ( REM ECHO "START enabled " @@ -139,7 +141,7 @@ IF "%startEnabled%" NEQ "" ( ) REM Check if controller is already running -for /F "TOKENS=1" %%G in ('%JAVA_HOME%\bin\jps.exe -lvV ^| find /I "opendaylight"') do ( +for /F "TOKENS=1" %%G in ('""!JAVA_H!" -lvV ^| find /I "opendaylight""') do ( SET PID=%%G ) if "!PID!" NEQ "" ( @@ -154,16 +156,125 @@ SET cp="%basedir%lib\org.eclipse.osgi-3.8.1.v20120830-144521.jar;%basedir%lib\or REM Now set framework classpath SET fwcp="file:\%basedir%lib\org.eclipse.osgi-3.8.1.v20120830-144521.jar,file:\%basedir%lib\org.eclipse.virgo.kernel.equinox.extensions-3.6.0.RELEASE.jar,file:\%basedir%lib\org.eclipse.equinox.launcher-1.3.0.v20120522-1813.jar" -SET RUN_CMD="%JAVA_HOME%\bin\java.exe" -Dopendaylight.controller %extraJVMOpts% -Djava.io.tmpdir="%basedir%work\tmp" -Djava.awt.headless=true -Dosgi.install.area=%basedir% -Dosgi.configuration.area="%basedir%configuration" -Dosgi.frameworkClassPath=%fwcp% -Dosgi.framework="file:\%basedir%lib\org.eclipse.osgi-3.8.1.v20120830-144521.jar" -classpath %cp% org.eclipse.equinox.launcher.Main %consoleOpts% +SET RUN_CMD="%JAVA_HOME%\bin\java.exe" -Dopendaylight.controller !extraJVMOpts! -Djava.io.tmpdir="%basedir%work\tmp" -Djava.awt.headless=true -Dosgi.install.area=%basedir% -Dosgi.configuration.area="%basedir%configuration" -Dosgi.frameworkClassPath=%fwcp% -Dosgi.framework="file:\%basedir%lib\org.eclipse.osgi-3.8.1.v20120830-144521.jar" -classpath %cp% org.eclipse.equinox.launcher.Main %consoleOpts% -ECHO %RUN_CMD% +ECHO !RUN_CMD! if "%startEnabled%" NEQ "" ( - START /B cmd /C CALL %RUN_CMD% > %basedir%\logs\controller.out 2>&1 + START /B cmd /C CALL !RUN_CMD! > %basedir%\logs\controller.out 2>&1 ECHO Running controller in the background. + EXIT /B 1 ) else ( - %RUN_CMD% + !RUN_CMD! EXIT /B %ERRORLEVEL% ) +:helper +echo. For more information on a specific command, type -help command-name. +echo. +echo jmx ^[-jmx^] +echo jmxport ^[-jmxport ^^] - DEFAULT is 1088 +echo debug ^[-debug^] +echo debugsuspend ^[-debugsuspend^] +echo debugport ^[-debugport ^^] - DEFAULT is 8000 +echo start ^[-start ^[^^]^] - DEFAULT port is 2400 +echo stop ^[-stop^] +echo status ^[-status^] +echo console ^[-console^] +echo agentpath ^[-agentpath:^^] +exit/B 1 + +:debugsuspend +ECHO. +ECHO. debugsuspend ^[-debugsuspend^] +ECHO. +ECHO. This command sets suspend on true in runjdwp in extra JVM options. If its true, VMStartEvent has a suspendPolicy of SUSPEND_ALL. If its false, VMStartEvent has a suspendPolicy of SUSPEND_NONE. +ECHO. +EXIT /B 1 + +:debugport +ECHO. +ECHO. debugport ^[-debugport ^^] - DEFAULT is 8000 +ECHO. +ECHO. Set address for settings in runjdwp in extra JVM options. +ECHO. The address is transport address for the connection. +ECHO. The address has to be in the range ^[1024,65535^]. If the option was not call, port will be set to default value. +ECHO. +EXIT /B 1 + +:jmxport +ECHO. +ECHO. jmxport ^[-jmxport ^^] - DEFAULT is 1088 +ECHO. +ECHO. Set jmx port for com.sun.management.jmxremote.port in JMX support. Port has to be in the range ^[1024,65535^]. If this option was not call, port will be set to default value. +ECHO. +EXIT /B 1 + +:debug +ECHO. +ECHO. debug [-debug] +ECHO. +ECHO. Run ODL controller with -Xdebug and -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=^$^{debugport^} +ECHO. +ECHO. -Xdebug enables debugging capabilities in the JVM which are used by the Java Virtual Machine Tools Interface (JVMTI). JVMTI is a low-level debugging interface used by debuggers and profiling tools. +ECHO. +ECHO. -Xrunjdwp option loads the JPDA reference implementation of JDWP. This library resides in the target VM and uses JVMDI and JNI to interact with it. It uses a transport and the JDWP protocol to communicate with a separate debugger application. +ECHO. +ECHO. settings for -Xrunjdwp: +ECHO. transport - name of the transport to use in connecting to debugger application +ECHO. server - if 'y', listen for a debugger application to attach; otherwise, attach to the debugger application at the specified address +ECHO. - if 'y' and no address is specified, choose a transport address at which to listen for a debugger application, and print the address to the standard output stream +ECHO. suspend - if 'y', VMStartEvent has a suspend Policy of SUSPEND_ALL +ECHO. - if 'n', VMStartEvent has a suspend policy of SUSPEND_NONE +ECHO. address - transport address for the connection +ECHO. - if server=n, attempt to attach to debugger application at this address +ECHO. - if server=y, listen for a connection at this address +ECHO. +EXIT /B 1 + +:jmx +ECHO. +ECHO. jmx [-jmx] +ECHO. +ECHO. Add JMX support. With settings for extra JVM options: -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=^$^{jmxport^} -Dcom.sun.management.jmxremote +ECHO. jmxport can by set with option -jmxport ^. Default num for the option is 1088. +ECHO. +EXIT /B 1 + +:stop +ECHO. +ECHO. stop ^[-stop^] +ECHO. +ECHO. If a controller is running, the command stop controller. Pid will be clean. +ECHO. +EXIT /B 1 + +:status +ECHO. +ECHO. status ^[-status^] +ECHO. +ECHO. Find out whether a controller is running and print it. +ECHO. +EXIT /B 1 + +:start +ECHO. +ECHO. start ^[-start ^[^^]^] +ECHO. +ECHO. If controller is not running, the command with argument^(for set port, where controller has start^) will start new controller on a port. The port has to be in the range ^[1024,65535^]. If this option was not call, port will be set to default value. Pid will be create. +EXIT /B 1 + +:console +ECHO. +ECHO. console [-console] +ECHO. Default option. +EXIT /B 1 + +:agentpath +ECHO. +ECHO. agentpath ^[-agentpath:^^] +ECHO. +ECHO. Agentpath option passes path to agent to jvm in order to load native agent library, e.g. yourkit profiler agent. +EXIT /B 1 + diff --git a/opendaylight/distribution/opendaylight/src/main/resources/run.sh b/opendaylight/distribution/opendaylight/src/main/resources/run.sh index 1e903d0650..13be2336b6 100755 --- a/opendaylight/distribution/opendaylight/src/main/resources/run.sh +++ b/opendaylight/distribution/opendaylight/src/main/resources/run.sh @@ -1,5 +1,20 @@ #!/bin/bash +##HELP +# For more information on a specific command, type -help command-name. +# +# jmx [-jmx] +# jmxport [-jmxport ] - DEFAULT is 1088 +# debug [-debug] +# debugsuspend [-debugsuspend] +# debugport [-debugport ] - DEFAULT is 8000 +# start [-start []] - DEFAULT port is 2400 +# stop [-stop] +# status [-status] +# console [-console] +# agentpath [-agentpath:] +## + platform='unknown' unamestr=`uname` if [[ "$unamestr" == 'Linux' ]]; then @@ -58,11 +73,6 @@ else datadir=${ODL_DATADIR} fi -function usage { - echo "Usage: $0 [-jmx] [-jmxport ] [-debug] [-debugsuspend] [-debugport ] [-start []] [-stop] [-status] [-console] [-help] [-agentpath:] []" - exit 1 -} - if [ -z ${TMP} ]; then pidfile="/tmp/opendaylight.PID" else @@ -82,13 +92,15 @@ stopdaemon=0 statusdaemon=0 consolestart=1 dohelp=0 -jvmMaxMemory="" +jvmMaxMemory="-Xmx1G" extraJVMOpts="" agentPath="" unknown_option=0 +helper="" while true ; do case "$1" in -debug) debug=1; shift ;; + -help) dohelp=1; shift; helper=$1; break ;; -jmx) startjmx=1; shift ;; -debugsuspend) debugsuspend=1; shift ;; -debugport) shift; debugportread="$1"; if [[ "${debugportread}" =~ ^[0-9]+$ ]] ; then debugport=${debugportread}; shift; else echo "-debugport expects a number but was not found"; exit -1; fi;; @@ -97,37 +109,39 @@ while true ; do -stop) stopdaemon=1; shift ;; -status) statusdaemon=1; shift ;; -console) shift ;; - -help) dohelp=1; shift;; -Xmx*) jvmMaxMemory="$1"; shift;; -D*) extraJVMOpts="${extraJVMOpts} $1"; shift;; -X*) extraJVMOpts="${extraJVMOpts} $1"; shift;; -agentpath:*) agentPath="$1"; shift;; "") break ;; - *) echo "Unknown option $1"; unknown_option=1; shift ;; + *) echo "Unknown option $1"; unknown_option=1; break ;; esac done -# Unknown Options and help + + if [ "${unknown_option}" -eq 1 ]; then - usage + echo "Use -help for more information." + exit 1 fi -if [ "${dohelp}" -eq 1 ]; then - usage -fi -if [ "${jvmMaxMemory}" == "" ]; then - jvmMaxMemory="-Xmx1G" - echo "*****************************************************************" - echo "JVM maximum memory was not defined. Setting maximum memory to 1G." - echo "To define the maximum memory, specify the -Xmx setting on the" - echo "command line. " - echo " e.g. ./run.sh -Xmx1G" - echo "*****************************************************************" +if [ "${dohelp}" -eq 1 ]; then + . ${basedir}/functions.sh + harvestHelp ${helper} + echo -e '\nFor other information type -help.\n' + exit 1 fi extraJVMOpts="${extraJVMOpts} ${jvmMaxMemory}" +##debugport +#debugport [-debugport ] - DEFAULT is 8000 +# +# Set address for settings in runjdwp in extra JVM options. +# The address is transport address for the connection. +# The address has to be in the range [1024,65535]. If this option was not call, port will be set to default value. +## # Validate debug port if [[ "${debugport}" -lt 1024 ]] || [[ "${debugport}" -gt 65535 ]]; then echo "Debug Port not in the range [1024,65535] ${debugport}" @@ -140,19 +154,51 @@ if [[ "${daemonport}" -lt 1024 ]] || [[ "${daemonport}" -gt 65535 ]]; then exit -1 fi +##jmxport +#jmxport [-jmxport ] - DEFAULT is 1088 +# +# Set jmx port for com.sun.management.jmxremote.port in JMX support. Port has to be in the range [1024,65535]. If this option was not call, port will be set to default value. +## # Validate jmx port if [[ "${jmxport}" -lt 1024 ]] || [[ "${jmxport}" -gt 65535 ]]; then echo "JMX Port not in the range [1024,65535] value is ${jmxport}" exit -1 fi +##debug +#debug [-debug] +# +#Run ODL controller with -Xdebug and -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=\${debugport} +#-Xdebug enables debugging capabilities in the JVM which are used by the Java Virtual Machine Tools Interface (JVMTI). JVMTI is a low-level debugging interface used by debuggers and profiling tools. +#-Xrunjdwp option loads the JPDA reference implementation of JDWP. This library resides in the target VM and uses JVMDI and JNI to interact with it. It uses a transport and the JDWP protocol to +#communicate with a separate debugger application. +#settings for -Xrunjdwp: +# transport - name of the transport to use in connecting to debugger application +# server - if “y”, listen for a debugger application to attach; otherwise, attach to the debugger application at the specified address +# - if “y” and no address is specified, choose a transport address at which to listen for a debugger application, and print the address to the standard output stream +# suspend - if “y”, VMStartEvent has a suspend Policy of SUSPEND_ALL +# - if “n”, VMStartEvent has a suspend policy of SUSPEND_NONE +# address - transport address for the connection +# - if server=n, attempt to attach to debugger application at this address +# - if server=y, listen for a connection at this address +## +##debugsuspend +#debugsuspend [-debugsuspend] +# +#This command sets suspend on true in runjdwp in extra JVM options. If its true, VMStartEvent has a suspendPolicy of SUSPEND_ALL. If its false, VMStartEvent has a suspendPolicy of SUSPEND_NONE. +## # Debug options if [ "${debugsuspend}" -eq 1 ]; then extraJVMOpts="${extraJVMOpts} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=${debugport}" elif [ "${debug}" -eq 1 ]; then extraJVMOpts="${extraJVMOpts} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=${debugport}" fi - +##jmx +#jmx [-jmx] +# +#Add JMX support. With settings for extra JVM options: -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=\${jmxport} -Dcom.sun.management.jmxremote +#jmxport can by set with command -jmxport . Default num for the option is 1088. +## # Add JMX support if [ "${startjmx}" -eq 1 ]; then extraJVMOpts="${extraJVMOpts} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=${jmxport} -Dcom.sun.management.jmxremote" @@ -182,6 +228,11 @@ FWCLASSPATH=${FWCLASSPATH},file:${basedir}/lib/org.eclipse.equinox.launcher-1.3. cd $basedir +##stop +#stop [-stop] +# +#If a controller is running, the command stop controller. Pid will be clean. +## if [ "${stopdaemon}" -eq 1 ]; then if [ -e "${pidfile}" ]; then daemonpid=`cat "${pidfile}"` @@ -195,6 +246,11 @@ if [ "${stopdaemon}" -eq 1 ]; then fi fi +##status +#status [-status] +# +#Find out whether a controller is running and print it. +## if [ "${statusdaemon}" -eq 1 ]; then if [ -e "${pidfile}" ]; then daemonpid=`cat "${pidfile}"` @@ -219,6 +275,22 @@ bdir=`echo "${basedir}" | sed 's/ /\\ /g'` confarea=`echo "${datadir}" | sed 's/ /\\ /g'` fwclasspath=`echo "${FWCLASSPATH}" | sed 's/ /\\ /g'` +##start +#start [-start []] +# +# If controller is not running, the command with argument(for set port, where controller has start) will start new controller on a port. The port has to be in the range [1024,65535]. If this option was not call, port will be set to default value. Pid will be create. +## +##console +#console [-console] +# +# Default option. +## +##agentpath +#agentpath [-agentpath:] +# +# Agentpath option passes path to agent to jvm in order to load native agent library, e.g. yourkit profiler agent. +## +echo "JVM maximum memory was set to ${jvmMaxMemory}." if [ "${startdaemon}" -eq 1 ]; then if [ -e "${pidfile}" ]; then echo "Another instance of controller running, check with $0 -status" diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java index 4da727f5c2..9fa68eeea6 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java @@ -22,6 +22,7 @@ import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.client.NetconfClientSession; import org.opendaylight.controller.netconf.client.NetconfClientSessionListener; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfiguration; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -82,8 +83,12 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, } public void initializeRemoteConnection(final NetconfClientDispatcher dispatch, - final NetconfReconnectingClientConfiguration config) { - dispatch.createReconnectingClient(config); + final NetconfClientConfiguration config) { + if(config instanceof NetconfReconnectingClientConfiguration) { + dispatch.createReconnectingClient((NetconfReconnectingClientConfiguration) config); + } + + dispatch.createClient(config); } private void tearDown( String reason ) { diff --git a/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java b/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java index 755ca75015..8bac0d211e 100644 --- a/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java +++ b/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java @@ -14,7 +14,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; - import org.apache.commons.lang3.BooleanUtils; import org.json.JSONArray; import org.json.JSONException; @@ -26,6 +25,7 @@ import org.opendaylight.yangtools.yang.model.api.ChoiceNode; import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; @@ -86,6 +86,8 @@ public class ModelGenerator { private static final String NUMBER = "number"; private static final String BOOLEAN = "boolean"; private static final String STRING = "string"; + private static final String ID_KEY = "id"; + private static final String SUB_TYPES_KEY = "subTypes"; private static final Map>, String> YANG_TYPE_TO_JSON_TYPE_MAPPING; @@ -116,6 +118,7 @@ public class ModelGenerator { JSONObject models = new JSONObject(); processContainers(module, models); processRPCs(module, models); + processIdentities(module, models); return models; } @@ -188,6 +191,58 @@ public class ModelGenerator { } } + /** + * Processes the 'identity' statement in a yang model + * and maps it to a 'model' in the Swagger JSON spec. + * + * @param module The module from which the identity stmt will be processed + * @param models The JSONObject in which the parsed identity will be put as a 'model' obj + * @throws JSONException + */ + private void processIdentities(Module module, JSONObject models) throws JSONException { + + String moduleName = module.getName(); + Set idNodes = module.getIdentities(); + _logger.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size()); + + for(IdentitySchemaNode idNode : idNodes){ + JSONObject identityObj=new JSONObject(); + String identityName = idNode.getQName().getLocalName(); + _logger.debug("Processing Identity: {}", identityName); + + identityObj.put(ID_KEY, identityName); + identityObj.put(DESCRIPTION_KEY, idNode.getDescription()); + + JSONObject props = new JSONObject(); + IdentitySchemaNode baseId = idNode.getBaseIdentity(); + + + if(baseId==null) { + /** + * This is a base identity. So lets see if + * it has sub types. If it does, then add them to the model definition. + */ + Set derivedIds = idNode.getDerivedIdentities(); + + if(derivedIds != null) { + JSONArray subTypes = new JSONArray(); + for(IdentitySchemaNode derivedId : derivedIds){ + subTypes.put(derivedId.getQName().getLocalName()); + } + identityObj.put(SUB_TYPES_KEY, subTypes); + } + } else { + /** + * This is a derived entity. Add it's base type & move on. + */ + props.put(TYPE_KEY, baseId.getQName().getLocalName()); + } + + //Add the properties. For a base type, this will be an empty object as required by the Swagger spec. + identityObj.put(PROPERTIES_KEY, props); + models.put(identityName, identityObj); + } + } /** * Processes the container node and populates the moduleJSON * @@ -446,7 +501,7 @@ public class ModelGenerator { processUnionType((UnionTypeDefinition) leafTypeDef, property); } else if (leafTypeDef instanceof IdentityrefTypeDefinition) { - property.putOpt(TYPE_KEY, "object"); + property.putOpt(TYPE_KEY, ((IdentityrefTypeDefinition) leafTypeDef).getIdentity().getQName().getLocalName()); } else if (leafTypeDef instanceof BinaryTypeDefinition) { processBinaryType((BinaryTypeDefinition) leafTypeDef, property); } else { diff --git a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java index 0ae33b8c71..29a27e2bb2 100644 --- a/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java +++ b/opendaylight/md-sal/statistics-manager/src/main/java/org/opendaylight/controller/md/statistics/manager/StatisticsRequestScheduler.java @@ -48,9 +48,13 @@ public class StatisticsRequestScheduler implements DataTransactionListener { private final TimerTask task = new TimerTask() { @Override public void run() { - long now = System.nanoTime(); - if(now > lastRequestTime+TimeUnit.MILLISECONDS.toNanos(REQUEST_MONITOR_INTERVAL)){ - requestStatistics(); + try{ + long now = System.nanoTime(); + if(now > lastRequestTime+TimeUnit.MILLISECONDS.toNanos(REQUEST_MONITOR_INTERVAL)){ + requestStatistics(); + } + }catch (IllegalArgumentException | IllegalStateException | NullPointerException e){ + srsLogger.warn("Exception occured while sending statistics request : {}",e); } } }; diff --git a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/TestingExceptionHandler.java b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/TestingExceptionHandler.java index 6fb231d847..3f04010015 100644 --- a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/TestingExceptionHandler.java +++ b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/TestingExceptionHandler.java @@ -7,20 +7,25 @@ */ package org.opendaylight.controller.netconf.persist.impl.osgi; -import org.junit.matchers.JUnitMatchers; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; +import org.junit.matchers.JUnitMatchers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + final class TestingExceptionHandler implements Thread.UncaughtExceptionHandler { + private static final Logger logger = LoggerFactory.getLogger(ConfigPersisterTest.class); + private Throwable t; @Override public void uncaughtException(Thread t, Throwable e) { + logger.debug("Uncaught exception in thread {}", t, e); this.t = e; } diff --git a/opendaylight/netconf/netconf-cli/README b/opendaylight/netconf/netconf-cli/README new file mode 100644 index 0000000000..c6a0af4f2d --- /dev/null +++ b/opendaylight/netconf/netconf-cli/README @@ -0,0 +1,113 @@ +usage: +Submit address + port for initial TCP connection (PURE TCP CONNECTIONS ARE NOT SUPPORTED YET) +Submit username + password in addition to address + port for initial SSH connection +If no arguments(or unexpected combination) is submitted, cli will be started without initial connection +To use with ODL controller, run with: java -jar netconf-cli-0.2.5-SNAPSHOT-executable.jar --server localhost --port 1830 --username admin --password admin + +Generic cli for netconf devices + +optional arguments: + -h, --help show this help message and exit + +TCP: + Base arguments to initiate TCP connection right away + + --server SERVER Netconf device ip-address/domain name + --port PORT Netconf device port + +SSH: + SSH credentials, if provided, initial connection will be attempted using SSH + + --username USERNAME Username for SSH connection + --password PASSWORD Password for SSH connection +------------------------------------------------------------------------ + +To run the cli execute: + +java -jar netconf-cli-0.2.5-SNAPSHOT-executable.jar --username user --password password --server serverIP --port optionalPort + +The cli will connect to the remote device automatically. +The initialization may take a few moments depending on the size of schemas provided by the device. +To view the progress, one can take a look inside netconfcli.log file (All logs are in this file starting with level TRACE). +Cli does not print any logging messages to the console, only to the file. + +------------------------------------------------------------------------ + +Correct initialization + connection should display following output: + +[maros@localhost target]$ java -jar netconf-cli-0.2.5-SNAPSHOT-executable.jar --server localhost --port 1830 --username admin --password admin +Connecting to localhost via SSH. Please wait. +Cli is up, available commands: + +add-flow(sal-flow) add-group(sal-group) +add-meter(sal-meter) begin-transaction(sal-remote) +cancel-commit(ietf-netconf) cancel-toast(toaster) +clear-toasts-made(toaster-provider-impl) close(netconf-cli) +close-session(ietf-netconf) commit(ietf-netconf) +connect(netconf-cli) copy-config(ietf-netconf) +create-data-change-event-subscription(sal-remote) create-notification-stream(sal-remote) +delete-config(ietf-netconf) discard-changes(ietf-netconf) +disconnect(netconf-cli) edit-config(ietf-netconf) +finish-transaction(flow-capable-transaction) get(ietf-netconf) +get-aggregate-flow-statistics-from-flow-table-for-all-flows(opendaylight-flow-statistics) get-aggregate-flow-statistics-from-flow-table-for-given-match(opendaylight-flow-statistics) +get-all-flow-statistics-from-flow-table(opendaylight-flow-statistics) get-all-flows-statistics-from-all-flow-tables(opendaylight-flow-statistics) +get-all-group-statistics(opendaylight-group-statistics) get-all-meter-config-statistics(opendaylight-meter-statistics) +get-all-meter-statistics(opendaylight-meter-statistics) get-all-node-connectors-statistics(opendaylight-port-statistics) +get-all-queues-statistics-from-all-ports(opendaylight-queue-statistics) get-all-queues-statistics-from-given-port(opendaylight-queue-statistics) +get-config(ietf-netconf) get-dead-events-count(threadpool-impl) +get-flow-statistics-from-flow-table(opendaylight-flow-statistics) get-flow-tables-statistics(opendaylight-flow-table-statistics) +get-group-description(opendaylight-group-statistics) get-group-features(opendaylight-group-statistics) +get-group-statistics(opendaylight-group-statistics) get-meter-features(opendaylight-meter-statistics) +get-meter-statistics(opendaylight-meter-statistics) get-next-transaction-id(flow-capable-transaction) +get-node-connector-statistics(opendaylight-port-statistics) get-queue(sal-queue) +get-queue-statistics-from-given-port(opendaylight-queue-statistics) get-schema(ietf-netconf-monitoring) +help(netconf-cli) kill-session(ietf-netconf) +lock(ietf-netconf) make-scrambled-with-wheat(kitchen-service-impl) +make-toast(toaster) remove-flow(sal-flow) +remove-group(sal-group) remove-meter(sal-meter) +reset(config-logging) restock-toaster(toaster) +shutdown(shutdown-impl) solicit-refresh(flow-topology-discovery) +transmit-packet(packet-processing) unlock(ietf-netconf) +update-flow(sal-flow) update-group(sal-group) +update-meter(sal-meter) update-port(sal-port) +update-table(sal-table) validate(ietf-netconf) + +netconf()> + + +------------------------------------------------------------------------ + +At this stage, any supported rpc can be invoked. To see all possible rpcs press TAB (serves as autocomplete). The output contains all the commands reported after at start-up + +------------------------------------------------------------------------ + +Example step-by-step execution of get-config rpc: + +1. Type get-config, hit TAB, hit enter +2. Cli will now walk all the input arguments of get-config rpc and ask for value +3. Cli asks for filter value +4. Submit filter (using TAB autocomplete) as a schema path or type "skip" (to not add any filter) and hit enter +5. Cli asks for config source (e.g. which data-store to query) +6. Use TAB to view options and submit either running or candidate data-store +7. Cli will display configuration of the remote device e.g. : + +data { + a { + address { + last-name a + first-name o + street aaaaa + } + address { + last-name a + first-name t + } + address { + last-name a + first-name y + } + } +} + + +------------------------------------------------------------------------ diff --git a/opendaylight/netconf/netconf-cli/README_ODL b/opendaylight/netconf/netconf-cli/README_ODL new file mode 100644 index 0000000000..3a15454df9 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/README_ODL @@ -0,0 +1,154 @@ +This file contains ODL controller specific examples: + +1A. Connecting to ODL controller automatically: + a. Make sure ODL controller is running on your or any other accessible device + b. Start the cli using this command (in folder controller/opendaylight/netconf/netconf-cli/target/): + java -jar netconf-cli-0.2.5-SNAPSHOT-executable.jar --server localhost --port 1830 --username admin --password admin + c. The cli will start up in aprox. 20 seconds (Schema download might take some time on the first connection, subsequent attempts should take less time) + d. You should see the list of commands avaliable in the controller e.g.: + add-flow(sal-flow) add-group(sal-group) + add-meter(sal-meter) begin-transaction(sal-remote) + cancel-commit(ietf-netconf) cancel-toast(toaster) + clear-toasts-made(toaster-provider-impl) close(netconf-cli) + close-session(ietf-netconf) commit(ietf-netconf) + connect(netconf-cli) copy-config(ietf-netconf) + create-data-change-event-subscription(sal-remote) .... + + +1B. Connecting to ODL from the CLI: + a. Make sure ODL controller is running on your or any other accessible device + b. Start the cli using this command (in folder controller/opendaylight/netconf/netconf-cli/target/): + java -jar netconf-cli-0.2.5-SNAPSHOT-executable.jar + c. The cli will start app right away (few seconds) + d. You should see only the basic commands e.g. connect, close, help, disconnect + e. Type connect, hit TAB, hit ENTER + f. Cli will ask for connect arguments: [address-name, address-port, user-name, user-password] + g. Address-name + The cli will ask what type of address you want to provide (domain-name or ip-address). This is caused by the yang model for connect command, the address-name is of type ietf-inet-types:host, which is a union of domain-name and ip-address. + Submit "domain-name" (TAB can be used for autocompete) + Now you need to provide value, submit "localhost" (TAB can be used for autocomplete, as "localhost" is the default value) + h. Address-port + Submit 1830 (default port for netconf SSH server in ODL) + i. User-name + Submit "admin" + j. User-password + Submit "admin" + k. The connection will be up in aprox. 20 seconds (Schema download might take some time on the first connection, subsequent attempts should take less time) + l. You should see the list of commands available in the controller + + +2. Disconnecting from ODL in the CLI + a. Execute scenario 1A or 1B + b. Type "disconn", hit TAB, hit Enter + c. You should see the following output: + status Connection disconnected + d. Use TAB to see available commands, only local commands are present now + e. Now you can use the connect command(as in 1B) to connect again + + +3. Using help command + a. Help command can be executed in connected as well as disconnected state + b. Type "help", hit TAB, hit Enter + c. You should see the help conent containing the list of all available commands with description for each of them e.g. + commands { + commands [id=close(netconf-cli)] { + id close(netconf-cli) + description Close the whole cli + } + commands [id=help(netconf-cli)] { + id help(netconf-cli) + description Display help + } + commands [id=disconnect(netconf-cli)] { + id disconnect(netconf-cli) + description Disconnect from a netconf device that is currently connected + } + commands [id=connect(netconf-cli)] { + id connect(netconf-cli) + description Connect to a remote netconf device, if not connected yet. Connection initialization is blocking and might take some time, depending on amount of yang schemas in remote device. + } + } + + +4. Executing get-config command (get-config(ietf-netconf)) + a. Execute scenario 1A or 1B + b. Type "get-config", hit TAB, hit Enter + c. Cli will ask for get-config arguments: [filter, source] + d. Filter + Submit "skip" (This will ignore the filter attribute, ODL does not support filtering at this moment, but will be supported in near future) + e. Source + You have to choose from candidate, running, startup. Submit running. + f. You should see the whole configuration of the ODL e.g.: + data { + modules { + module { + module [name=toaster-provider-impl] { + name toaster-provider-impl + type (urn:opendaylight:params:xml:ns:yang:controller:config:toaster-provider:impl?revision=2014-01-31)toaster-provider-impl + choice configuration (toaster-provider-impl) { + ... + + +5. Executing get command (get(ietf-netconf)) + a. Execute scenario 1A or 1B + b. Type "get(", hit TAB, hit Enter + c. Cli will ask for get arguments: [filter] + d. Filter + Submit "skip" (This will ignore the filter attribute, ODL does not support filtering at this moment, but will be supported in near future) + f. You should see the whole data-tree of the ODL + + +6. Executing edit-config command (edit-config(ietf-netconf)) + a. Execute scenario 1A or 1B + b. Type "edit", hit TAB, hit Enter + c. Cli will ask for edit-config arguments: [default-operation, edit-content, error-option, target, test-option] + d. Config + Config contains the data to be edited + 1. First you have to specify a path pointing to a concrete data node. Use TAB to help with autocomplete. + Submit "modules(config)/module(config)/" + Module node is of type list and now you have to construct a whole new list entry for the module list. + 2. The cli will ask for these nodes: [configuration, name, type] + Name - Submit any name e.g. newModule + Type - For Type you have to pick from available module types in the ODL, hit TAB for hints + Submit "threadfactory-naming(threadpool-impl)" to create a new instance of threadfactory in the ODL. + Configuration - For configuration you have to pick from available module types again + Submit "threadfactory-naming" to match previous module type + The cli will now ask for threadfactory-naming specific configuration: [prefix] + Prefix - Submit any string + + The cli will now if you want to create another module entry. + Submit "N". + e. Default-operation + Submit "skip" to skip or pick from available e.g. merge, replace etc. + f. Error-option + Submit "skip" to skip option. + g. Config-target + This is a choice between running and candidate. Submit candidate to edit configuration only in the candidate datastore. + h. Test-option + Submit "skip" to skip option. + i. You should see OK response + j. You can check the candidate datastore change by executing get-config command as in scenario 4, but pick candidate as the source. + k. You should see this module in the output: + module [name=newModule] { + name newModule + type (urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl?revision=2013-04-05)threadfactory-naming + choice configuration (threadfactory-naming) { + name-prefix prefix + } + } + + +7. Commiting changes from candidate to running datastore + a. Execute scenario 6. + b. Type commit, hit TAB, hit Enter + c. Cli will ask for commit arguments: [confirm-timeout, confirmed, persist, persist-id]. We will skip all these arguments since they are not supported in ODL. Cli should be able to detect this and not ask for them. This is a TODO, by supporting feature/if-feature detection in the CLI. + d. Confirm-timeout + Skip + e. Confirmed + N + f. Persist + Skip + g. Persist-id + Skip + h. You should see OK response + i. You can check the candidate datastore change by executing get-config command as in scenario 4. diff --git a/opendaylight/netconf/netconf-cli/pom.xml b/opendaylight/netconf/netconf-cli/pom.xml new file mode 100644 index 0000000000..cd7a1aacf2 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/pom.xml @@ -0,0 +1,109 @@ + + + + + 4.0.0 + + + org.opendaylight.controller + netconf-subsystem + 0.2.5-SNAPSHOT + + netconf-cli + jar + ${project.artifactId} + + + + + + ch.qos.logback + logback-classic + + + + jline + jline + 2.11 + + + net.sourceforge.argparse4j + argparse4j + 0.4.3 + + + org.opendaylight.controller + sal-core-api + + + org.opendaylight.controller + sal-netconf-connector + + + org.opendaylight.yangtools + mockito-configuration + + + org.opendaylight.yangtools + yang-data-impl + + + + org.opendaylight.yangtools + yang-data-json + 0.6.2-SNAPSHOT + + + org.opendaylight.yangtools + yang-model-api + + + org.opendaylight.yangtools + yang-parser-impl + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.opendaylight.controller.netconf.cli.Main + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + + + shade + + package + + + + org.opendaylight.controller.netconf.cli.Main + + + true + executable + + + + + + + diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/Cli.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/Cli.java new file mode 100644 index 0000000000..a49c7b9b74 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/Cli.java @@ -0,0 +1,218 @@ +/* + * 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.netconf.cli; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import jline.console.UserInterruptException; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; +import org.opendaylight.controller.netconf.cli.commands.Command; +import org.opendaylight.controller.netconf.cli.commands.CommandConstants; +import org.opendaylight.controller.netconf.cli.commands.CommandDispatcher; +import org.opendaylight.controller.netconf.cli.commands.CommandInvocationException; +import org.opendaylight.controller.netconf.cli.commands.input.Input; +import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition; +import org.opendaylight.controller.netconf.cli.commands.output.Output; +import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.controller.netconf.cli.writer.WriteException; +import org.opendaylight.controller.netconf.cli.writer.Writer; +import org.opendaylight.controller.netconf.cli.writer.impl.CompositeNodeWriter; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode; + +/** + * The top level cli state that dispatches command executions + */ +public class Cli implements Runnable { + private final CommandDispatcher commandRegistry; + private final CommandArgHandlerRegistry argumentHandlerRegistry; + private final SchemaContextRegistry schemaContextRegistry; + private final ConsoleIO consoleIO; + + public Cli(final ConsoleIO consoleIO, final CommandDispatcher commandRegistry, + final CommandArgHandlerRegistry argumentHandlerRegistry, final SchemaContextRegistry schemaContextRegistry) { + this.consoleIO = consoleIO; + this.commandRegistry = commandRegistry; + this.argumentHandlerRegistry = argumentHandlerRegistry; + this.schemaContextRegistry = schemaContextRegistry; + } + + @Override + public void run() { + try { + consoleIO.writeLn("Cli is up, available commands:"); + final RootConsoleContext consoleContext = new RootConsoleContext(commandRegistry); + consoleIO.enterContext(consoleContext); + consoleIO.complete(); + consoleIO.writeLn(""); + + while (true) { + final String commandName = consoleIO.read(); + final Optional commandOpt = commandRegistry.getCommand(commandName); + + if (commandOpt.isPresent() == false) { + continue; + } + + final Command command = commandOpt.get(); + try { + consoleIO.enterContext(command.getConsoleContext()); + final Output response = command.invoke(handleInput(command.getInputDefinition())); + handleOutput(command, response); + } catch (final CommandInvocationException e) { + consoleIO.write(e.getMessage()); + } catch (final UserInterruptException e) { + consoleIO.writeLn("Command " + command.getCommandId() + " was terminated."); + } finally { + consoleIO.leaveContext(); + } + + } + } catch (final IOException e) { + throw new RuntimeException("IO failure", e); + } + } + + private void handleOutput(final Command command, final Output response) { + final OutputDefinition outputDefinition = command.getOutputDefinition(); + + final Writer outHandler = argumentHandlerRegistry.getGenericWriter(); + if (outputDefinition.isEmpty()) { + handleEmptyOutput(command, response); + } else { + handleRegularOutput(response, outputDefinition, outHandler); + } + } + + private void handleRegularOutput(final Output response, final OutputDefinition outputDefinition, + final Writer outHandler) { + final Map>> unwrap = response.unwrap(outputDefinition); + + for (final DataSchemaNode schemaNode : unwrap.keySet()) { + Preconditions.checkNotNull(schemaNode); + + try { + + // FIXME move custom writer to GenericWriter/Serializers ... + // this checks only first level + final Optional>> customReaderClassOpt = tryGetCustomHandler(schemaNode); + + if (customReaderClassOpt.isPresent()) { + final Writer customReaderInstance = argumentHandlerRegistry + .getCustomWriter(customReaderClassOpt.get()); + Preconditions.checkNotNull(customReaderInstance, "Unknown custom writer: %s", + customReaderClassOpt.get()); + customReaderInstance.write(schemaNode, unwrap.get(schemaNode)); + } else { + outHandler.write(schemaNode, unwrap.get(schemaNode)); + } + + } catch (final WriteException e) { + throw new IllegalStateException("Unable to write value for: " + schemaNode.getQName() + " from: " + + unwrap.get(schemaNode), e); + } + } + } + + private void handleEmptyOutput(final Command command, final Output response) { + try { + new CompositeNodeWriter(consoleIO, new OutFormatter()).write(null, + Collections.> singletonList(response.getOutput())); + } catch (final WriteException e) { + throw new IllegalStateException("Unable to write value for: " + response.getOutput().getNodeType() + + " from: " + command.getCommandId(), e); + } + } + + private Input handleInput(final InputDefinition inputDefinition) { + List> allArgs = Collections.emptyList(); + try { + if (!inputDefinition.isEmpty()) { + allArgs = argumentHandlerRegistry.getGenericReader(schemaContextRegistry.getLocalSchemaContext()).read( + inputDefinition.getInput()); + } + } catch (final ReadingException e) { + throw new IllegalStateException("Unable to read value for: " + inputDefinition.getInput().getQName(), e); + } + + return new Input(allArgs); + } + + // TODO move tryGet to GenericWriter, GenericReader has the same code + private Optional> tryGetCustomHandler(final DataSchemaNode dataSchemaNode) { + + for (final UnknownSchemaNode unknownSchemaNode : dataSchemaNode.getUnknownSchemaNodes()) { + + if (isExtenstionForCustomHandler(unknownSchemaNode)) { + final String argumentHandlerClassName = unknownSchemaNode.getNodeParameter(); + try { + final Class argumentClass = Class.forName(argumentHandlerClassName); + // TODO add check before cast + return Optional.> of((Class) argumentClass); + } catch (final ClassNotFoundException e) { + throw new IllegalArgumentException("Unknown custom reader class " + argumentHandlerClassName + + " for: " + dataSchemaNode.getQName()); + } + } + } + + return Optional.absent(); + } + + private boolean isExtenstionForCustomHandler(final UnknownSchemaNode unknownSchemaNode) { + final QName qName = unknownSchemaNode.getExtensionDefinition().getQName(); + return qName.equals(CommandConstants.ARG_HANDLER_EXT_QNAME); + } + + private static final class RootConsoleContext implements ConsoleContext { + + private final Completer completer; + + public RootConsoleContext(final CommandDispatcher commandRegistry) { + completer = new CommandCompleter(commandRegistry); + } + + @Override + public Completer getCompleter() { + return completer; + } + + @Override + public Optional getPrompt() { + return Optional.absent(); + } + + private class CommandCompleter extends StringsCompleter { + + private final CommandDispatcher commandRegistry; + + public CommandCompleter(final CommandDispatcher commandRegistry) { + this.commandRegistry = commandRegistry; + } + + @Override + public int complete(final String buffer, final int cursor, final List candidates) { + getStrings().clear(); + getStrings().addAll(commandRegistry.getCommandIds()); + return super.complete(buffer, cursor, candidates); + } + } + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/CommandArgHandlerRegistry.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/CommandArgHandlerRegistry.java new file mode 100644 index 0000000000..2eab22a6bd --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/CommandArgHandlerRegistry.java @@ -0,0 +1,153 @@ +/* + * 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.netconf.cli; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import java.util.Map; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.Reader; +import org.opendaylight.controller.netconf.cli.reader.custom.ConfigReader; +import org.opendaylight.controller.netconf.cli.reader.custom.EditContentReader; +import org.opendaylight.controller.netconf.cli.reader.custom.FilterReader; +import org.opendaylight.controller.netconf.cli.reader.custom.PasswordReader; +import org.opendaylight.controller.netconf.cli.reader.impl.GenericReader; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.controller.netconf.cli.writer.Writer; +import org.opendaylight.controller.netconf.cli.writer.custom.DataWriter; +import org.opendaylight.controller.netconf.cli.writer.impl.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * Keeps custom and generic input/output arguments handlers. Custom handlers are + * constructed lazily, due to remote schema context acquisition. + */ +public class CommandArgHandlerRegistry { + + private final ConsoleIO consoleIO; + private final SchemaContextRegistry schemaContextRegistry; + + private final Map>, ReaderProvider> customReaders = Maps + .newHashMap(); + private final Map>, WriterProvider> customWriters = Maps.newHashMap(); + + public CommandArgHandlerRegistry(final ConsoleIO consoleIO, final SchemaContextRegistry schemaContextRegistry) { + this.consoleIO = consoleIO; + this.schemaContextRegistry = schemaContextRegistry; + + setUpReaders(); + setUpWriters(); + } + + private void setUpWriters() { + customWriters.put(DataWriter.class, new DataWriterProvider()); + } + + private void setUpReaders() { + customReaders.put(PasswordReader.class, new PasswordReaderProvider()); + customReaders.put(FilterReader.class, new FilterReaderProvider()); + customReaders.put(ConfigReader.class, new ConfigReaderProvider()); + customReaders.put(EditContentReader.class, new EditContentReaderProvider()); + } + + public synchronized Reader getCustomReader( + final Class> readerType) { + return customReaders.get(readerType).provide(consoleIO, this, schemaContextRegistry); + } + + private static SchemaContext getRemoteSchema(final Class handlerType, + final SchemaContextRegistry schemaContextRegistry) { + final Optional remoteSchemaContext = schemaContextRegistry.getRemoteSchemaContext(); + Preconditions.checkState(remoteSchemaContext.isPresent(), + "Remote schema context not acquired yet, cannot get handler %s", handlerType); + return remoteSchemaContext.get(); + } + + public synchronized Reader getGenericReader(final SchemaContext schemaContext) { + return new GenericReader(consoleIO, this, schemaContext); + } + + public synchronized Reader getGenericReader(final SchemaContext schemaContext, + final boolean readConfigNode) { + return new GenericReader(consoleIO, this, schemaContext, readConfigNode); + } + + public synchronized Writer getCustomWriter(final Class> writerType) { + return customWriters.get(writerType).provide(consoleIO, getRemoteSchema(writerType, schemaContextRegistry), + this); + } + + public synchronized Writer getGenericWriter() { + return new NormalizedNodeWriter(consoleIO, new OutFormatter()); + } + + /** + * Reader providers, in order to construct readers lazily + */ + private static interface ReaderProvider { + Reader provide(ConsoleIO consoleIO, + final CommandArgHandlerRegistry commandArgHandlerRegistry, + final SchemaContextRegistry schemaContextRegistry); + } + + private static final class FilterReaderProvider implements ReaderProvider { + @Override + public Reader provide(final ConsoleIO consoleIO, + final CommandArgHandlerRegistry commandArgHandlerRegistry, + final SchemaContextRegistry schemaContextRegistry) { + return new FilterReader(consoleIO, getRemoteSchema(FilterReader.class, schemaContextRegistry)); + } + } + + private static final class ConfigReaderProvider implements ReaderProvider { + @Override + public Reader provide(final ConsoleIO consoleIO, + final CommandArgHandlerRegistry commandArgHandlerRegistry, + final SchemaContextRegistry schemaContextRegistry) { + return new ConfigReader(consoleIO, getRemoteSchema(ConfigReader.class, schemaContextRegistry), + commandArgHandlerRegistry); + } + } + + private static final class EditContentReaderProvider implements ReaderProvider { + @Override + public Reader provide(final ConsoleIO consoleIO, + final CommandArgHandlerRegistry commandArgHandlerRegistry, + final SchemaContextRegistry schemaContextRegistry) { + return new EditContentReader(consoleIO, commandArgHandlerRegistry, getRemoteSchema(EditContentReader.class, + schemaContextRegistry)); + } + } + + private static final class PasswordReaderProvider implements ReaderProvider { + @Override + public Reader provide(final ConsoleIO consoleIO, + final CommandArgHandlerRegistry commandArgHandlerRegistry, + final SchemaContextRegistry schemaContextRegistry) { + return new PasswordReader(consoleIO, schemaContextRegistry.getLocalSchemaContext()); + } + } + + /** + * Writer providers, in order to construct readers lazily + */ + private static interface WriterProvider { + Writer provide(ConsoleIO consoleIO, SchemaContext schema, + final CommandArgHandlerRegistry commandArgHandlerRegistry); + } + + private class DataWriterProvider implements WriterProvider { + @Override + public Writer provide(final ConsoleIO consoleIO, final SchemaContext schema, + final CommandArgHandlerRegistry commandArgHandlerRegistry) { + return new DataWriter(consoleIO, new OutFormatter(), schema); + } + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/Main.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/Main.java new file mode 100644 index 0000000000..8605501a9c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/Main.java @@ -0,0 +1,231 @@ +/* + * 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.netconf.cli; + +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import net.sourceforge.argparse4j.ArgumentParsers; +import net.sourceforge.argparse4j.inf.ArgumentGroup; +import net.sourceforge.argparse4j.inf.ArgumentParser; +import net.sourceforge.argparse4j.inf.ArgumentParserException; +import net.sourceforge.argparse4j.inf.Namespace; +import org.opendaylight.controller.netconf.cli.commands.CommandDispatcher; +import org.opendaylight.controller.netconf.cli.commands.local.Connect; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.io.ConsoleIOImpl; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder; +import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * Parse arguments, start remote device connection and start CLI after the + * connection is fully up + */ +public class Main { + + public static void main(final String[] args) { + final CliArgumentParser cliArgs = new CliArgumentParser(); + try { + cliArgs.parse(args); + } catch (final ArgumentParserException e) { + // Just end the cli, exception was handled by the CliArgumentParser + return; + } + + final ConsoleIO consoleIO; + try { + consoleIO = new ConsoleIOImpl(); + } catch (final IOException e) { + handleStartupException(e); + return; + } + + final SchemaContext localSchema = CommandDispatcher.parseSchema(CommandDispatcher.LOCAL_SCHEMA_PATHS); + final SchemaContextRegistry schemaContextRegistry = new SchemaContextRegistry(localSchema); + + final CommandDispatcher commandDispatcher = new CommandDispatcher(); + final CommandArgHandlerRegistry argumentHandlerRegistry = new CommandArgHandlerRegistry(consoleIO, + schemaContextRegistry); + final NetconfDeviceConnectionManager connectionManager = new NetconfDeviceConnectionManager(commandDispatcher, + argumentHandlerRegistry, schemaContextRegistry, consoleIO); + + commandDispatcher.addLocalCommands(connectionManager, localSchema, cliArgs.getConnectionTimeoutMs()); + + switch (cliArgs.connectionArgsPresent()) { + case TCP: { + // FIXME support pure TCP + handleRunningException(new UnsupportedOperationException("PURE TCP CONNECTIONS ARE NOT SUPPORTED YET, USE SSH INSTEAD BY PROVIDING USERNAME AND PASSWORD AS WELL")); + return; + } + case SSH: { + writeStatus(consoleIO, "Connecting to %s via SSH. Please wait.", cliArgs.getAddress()); + connectionManager.connectBlocking(cliArgs.getAddress(), getClientSshConfig(cliArgs)); + break; + } + case NONE: {/* Do not connect initially */ + writeStatus(consoleIO, "No initial connection. To connect use the connect command"); + } + } + + try { + new Cli(consoleIO, commandDispatcher, argumentHandlerRegistry, schemaContextRegistry).run(); + } catch (final Exception e) { + // TODO Running exceptions have to be handled properly + handleRunningException(e); + System.exit(0); + } + } + + private static NetconfClientConfigurationBuilder getClientConfig(final CliArgumentParser cliArgs) { + return NetconfClientConfigurationBuilder.create().withAddress(cliArgs.getServerAddress()) + .withConnectionTimeoutMillis(cliArgs.getConnectionTimeoutMs()) + .withReconnectStrategy(Connect.getReconnectStrategy()) + .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP); + } + + private static NetconfClientConfigurationBuilder getClientSshConfig(final CliArgumentParser cliArgs) { + return NetconfClientConfigurationBuilder.create().withAddress(cliArgs.getServerAddress()) + .withConnectionTimeoutMillis(cliArgs.getConnectionTimeoutMs()) + .withReconnectStrategy(Connect.getReconnectStrategy()) + .withAuthHandler(cliArgs.getCredentials()) + .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH); + } + + private static void handleStartupException(final IOException e) { + handleException(e, "Unable to initialize CLI"); + } + + private static void handleException(final Exception e, final String message) { + System.err.println(message); + e.printStackTrace(System.err); + } + + private static void writeStatus(final ConsoleIO io, final String blueprint, final Object... args) { + try { + io.formatLn(blueprint, args); + } catch (final IOException e) { + handleStartupException(e); + } + } + + private static void handleRunningException(final Exception e) { + handleException(e, "Unexpected CLI runtime exception"); + } + + private static final class CliArgumentParser { + + public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; + public static final String SERVER = "server"; + public static final String PORT = "port"; + + public static final String CONNECT_TIMEOUT = "connectionTimeout"; + public static final int DEFAULT_CONNECTION_TIMEOUT_MS = 50000; + + private final ArgumentParser parser; + private Namespace parsed; + + private CliArgumentParser() { + parser = ArgumentParsers.newArgumentParser("Netconf cli").defaultHelp(true) + .description("Generic cli for netconf devices") + .usage("Submit address + port for initial TCP connection (PURE TCP CONNECTIONS ARE NOT SUPPORTED YET)\n" + + "Submit username + password in addition to address + port for initial SSH connection\n" + + "If no arguments(or unexpected combination) is submitted, cli will be started without initial connection\n" + + "To use with ODL controller, run with: java -jar netconf-cli-0.2.5-SNAPSHOT-executable.jar --server localhost --port 1830 --username admin --password admin"); + + final ArgumentGroup tcpGroup = parser.addArgumentGroup("TCP") + .description("Base arguments to initiate TCP connection right away"); + + tcpGroup.addArgument("--" + SERVER).help("Netconf device ip-address/domain name"); + tcpGroup.addArgument("--" + PORT).type(Integer.class).help("Netconf device port"); + tcpGroup.addArgument("--" + CONNECT_TIMEOUT) + .type(Integer.class) + .setDefault(DEFAULT_CONNECTION_TIMEOUT_MS) + .help("Timeout(in ms) for connection to succeed, if the connection is not fully established by the time is up, " + + "connection attempt is considered a failure. This attribute is not working as expected yet"); + + final ArgumentGroup sshGroup = parser.addArgumentGroup("SSH") + .description("SSH credentials, if provided, initial connection will be attempted using SSH"); + + sshGroup.addArgument("--" + USERNAME).help("Username for SSH connection"); + sshGroup.addArgument("--" + PASSWORD).help("Password for SSH connection"); + } + + public void parse(final String[] args) throws ArgumentParserException { + try { + this.parsed = parser.parseArgs(args); + } catch (final ArgumentParserException e) { + parser.handleError(e); + throw e; + } + } + + public InetSocketAddress getServerAddress() { + try { + return new InetSocketAddress(InetAddress.getByName(getAddress()), getPort()); + } catch (final UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } + + private Integer getPort() { + checkParsed(); + return parsed.getInt(PORT); + } + + private String getAddress() { + checkParsed(); + return getString(SERVER); + } + + private Integer getConnectionTimeoutMs() { + checkParsed(); + return parsed.getInt(CONNECT_TIMEOUT); + } + + private void checkParsed() { + Preconditions.checkState(parsed != null, "No arguments were parsed yet"); + } + + public String getUsername() { + checkParsed(); + return getString(USERNAME); + } + + private String getString(final String key) { + return parsed.getString(key); + } + + public LoginPassword getCredentials() { + return new LoginPassword(getUsername(), getPassword()); + } + + public String getPassword() { + checkParsed(); + return getString(PASSWORD); + } + + public InitialConnectionType connectionArgsPresent() { + if(getAddress() != null && getPort() != null) { + if(getUsername() != null && getPassword() != null) { + return InitialConnectionType.SSH; + } + return InitialConnectionType.TCP; + } + return InitialConnectionType.NONE; + } + + enum InitialConnectionType { + TCP, SSH, NONE + } + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/NetconfDeviceConnectionHandler.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/NetconfDeviceConnectionHandler.java new file mode 100644 index 0000000000..bd092bc5bd --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/NetconfDeviceConnectionHandler.java @@ -0,0 +1,93 @@ +/* + * 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.netconf.cli; + +import com.google.common.base.Optional; +import jline.console.completer.Completer; +import jline.console.completer.NullCompleter; +import org.opendaylight.controller.netconf.cli.commands.CommandDispatcher; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; + +/** + * Implementation of RemoteDeviceHandler. Integrates cli with + * sal-netconf-connector. + */ +public class NetconfDeviceConnectionHandler implements RemoteDeviceHandler { + + private final CommandDispatcher commandDispatcher; + private final SchemaContextRegistry schemaContextRegistry; + private final ConsoleIO console; + private final String deviceId; + + private boolean up = false; + + public NetconfDeviceConnectionHandler(final CommandDispatcher commandDispatcher, + final SchemaContextRegistry schemaContextRegistry, final ConsoleIO console, final String deviceId) { + this.commandDispatcher = commandDispatcher; + this.schemaContextRegistry = schemaContextRegistry; + this.console = console; + this.deviceId = deviceId; + } + + @Override + public synchronized void onDeviceConnected(final SchemaContextProvider contextProvider, + final NetconfSessionCapabilities capabilities, final RpcImplementation rpcImplementation) { + console.enterRootContext(new ConsoleContext() { + + @Override + public Optional getPrompt() { + return Optional.of(deviceId); + } + + @Override + public Completer getCompleter() { + return new NullCompleter(); + } + }); + + // TODO Load schemas for base netconf + inet types from remote device if + // possible + // TODO detect netconf base version + // TODO detect inet types version + commandDispatcher.addRemoteCommands(rpcImplementation, contextProvider.getSchemaContext()); + schemaContextRegistry.setRemoteSchemaContext(contextProvider.getSchemaContext()); + up = true; + this.notify(); + } + + /** + * @return true if connection was fully established + */ + public synchronized boolean isUp() { + return up; + } + + @Override + public synchronized void onDeviceDisconnected() { + console.leaveRootContext(); + commandDispatcher.removeRemoteCommands(); + schemaContextRegistry.setRemoteSchemaContext(null); + up = false; + } + + @Override + public void onNotification(final CompositeNode compositeNode) { + // FIXME + } + + @Override + public void close() { + // FIXME + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/NetconfDeviceConnectionManager.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/NetconfDeviceConnectionManager.java new file mode 100644 index 0000000000..3dd892e169 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/NetconfDeviceConnectionManager.java @@ -0,0 +1,122 @@ +/* + * 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.netconf.cli; + +import com.google.common.base.Preconditions; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.util.HashedWheelTimer; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.opendaylight.controller.netconf.cli.commands.CommandDispatcher; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder; +import org.opendaylight.controller.sal.connect.netconf.NetconfDevice; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yangtools.yang.model.util.repo.AbstractCachingSchemaSourceProvider; +import org.opendaylight.yangtools.yang.model.util.repo.FilesystemSchemaCachingProvider; +import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProvider; +import org.opendaylight.yangtools.yang.model.util.repo.SchemaSourceProviders; + +/** + * Manages connect/disconnect to 1 remote device + */ +public class NetconfDeviceConnectionManager implements Closeable { + + private final CommandDispatcher commandDispatcher; + private final SchemaContextRegistry schemaContextRegistry; + private final ConsoleIO console; + + private final ExecutorService executor; + private final NioEventLoopGroup nettyThreadGroup; + private final NetconfClientDispatcherImpl netconfClientDispatcher; + + // Connection + private NetconfDeviceConnectionHandler handler; + private NetconfDevice device; + private NetconfDeviceCommunicator listener; + + public NetconfDeviceConnectionManager(final CommandDispatcher commandDispatcher, + final CommandArgHandlerRegistry argumentHandlerRegistry, final SchemaContextRegistry schemaContextRegistry, + final ConsoleIO consoleIO) { + this.commandDispatcher = commandDispatcher; + this.schemaContextRegistry = schemaContextRegistry; + this.console = consoleIO; + + executor = Executors.newSingleThreadExecutor(); + nettyThreadGroup = new NioEventLoopGroup(); + netconfClientDispatcher = new NetconfClientDispatcherImpl(nettyThreadGroup, nettyThreadGroup, + new HashedWheelTimer()); + } + + // TODO we receive configBuilder in order to add SessionListener, Session + // Listener should not be part of config + public synchronized void connect(final String name, final NetconfClientConfigurationBuilder configBuilder) { + // TODO change IllegalState exceptions to custom ConnectionException + Preconditions.checkState(listener == null, "Already connected"); + + final RemoteDeviceId deviceId = new RemoteDeviceId(name); + + handler = new NetconfDeviceConnectionHandler(commandDispatcher, schemaContextRegistry, + console, name); + device = NetconfDevice.createNetconfDevice(deviceId, getGlobalNetconfSchemaProvider(), executor, handler); + listener = new NetconfDeviceCommunicator(deviceId, device); + configBuilder.withSessionListener(listener); + listener.initializeRemoteConnection(netconfClientDispatcher, configBuilder.build()); + } + + /** + * Blocks thread until connection is fully established + */ + public synchronized Set connectBlocking(final String name, final NetconfClientConfigurationBuilder configBuilder) { + this.connect(name, configBuilder); + synchronized (handler) { + while (handler.isUp() == false) { + try { + // TODO implement Timeout for unsuccessful connection + handler.wait(); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalArgumentException(e); + } + } + } + + return commandDispatcher.getRemoteCommandIds(); + } + + public synchronized void disconnect() { + Preconditions.checkState(listener != null, "Not connected yet"); + Preconditions.checkState(handler.isUp(), "Not connected yet"); + listener.close(); + listener = null; + device = null; + handler.close(); + handler = null; + } + + private static AbstractCachingSchemaSourceProvider getGlobalNetconfSchemaProvider() { + // FIXME move to args + final String storageFile = "cache/schema"; + final File directory = new File(storageFile); + final SchemaSourceProvider defaultProvider = SchemaSourceProviders.noopProvider(); + return FilesystemSchemaCachingProvider.createFromStringSourceProvider(defaultProvider, directory); + } + + @Override + public void close() throws IOException { + executor.shutdownNow(); + nettyThreadGroup.shutdownGracefully(); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/SchemaContextRegistry.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/SchemaContextRegistry.java new file mode 100644 index 0000000000..5c3cfe79d3 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/SchemaContextRegistry.java @@ -0,0 +1,38 @@ +/* + * 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.netconf.cli; + +import com.google.common.base.Optional; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * Contains the local schema context (containing local commands) and remote schema context (remote commands) + * + * Remote commands are set only after the connection is fully established. So classes using the remote schema context + */ +public class SchemaContextRegistry { + + private final SchemaContext localSchemaContext; + private SchemaContext remoteSchemaContext; + + public SchemaContextRegistry(final SchemaContext localSchemaContext) { + this.localSchemaContext = localSchemaContext; + } + + public synchronized Optional getRemoteSchemaContext() { + return Optional.fromNullable(remoteSchemaContext); + } + + public SchemaContext getLocalSchemaContext() { + return localSchemaContext; + } + + public synchronized void setRemoteSchemaContext(final SchemaContext remoteSchemaContext) { + this.remoteSchemaContext = remoteSchemaContext; + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/AbstractCommand.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/AbstractCommand.java new file mode 100644 index 0000000000..f02ce747bc --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/AbstractCommand.java @@ -0,0 +1,80 @@ +/* + * 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.netconf.cli.commands; + +import com.google.common.base.Optional; +import jline.console.completer.Completer; +import jline.console.completer.NullCompleter; +import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition; +import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; + +public abstract class AbstractCommand implements Command { + + private final QName qName; + private final InputDefinition args; + private final OutputDefinition output; + private final String description; + + public AbstractCommand(final QName qName, final InputDefinition args, final OutputDefinition output, + final String description) { + this.qName = qName; + this.args = args; + this.output = output; + this.description = description; + } + + protected static OutputDefinition getOutputDefinition(final RpcDefinition rpcDefinition) { + final ContainerSchemaNode output = rpcDefinition.getOutput(); + return output != null ? OutputDefinition.fromOutput(output) : OutputDefinition.empty(); + } + + protected static InputDefinition getInputDefinition(final RpcDefinition rpcDefinition) { + final ContainerSchemaNode input = rpcDefinition.getInput(); + return InputDefinition.fromInput(input); + } + + @Override + public InputDefinition getInputDefinition() { + return args; + } + + @Override + public OutputDefinition getOutputDefinition() { + return output; + } + + @Override + public QName getCommandId() { + return qName; + } + + @Override + public ConsoleContext getConsoleContext() { + return new ConsoleContext() { + + @Override + public Completer getCompleter() { + return new NullCompleter(); + } + + @Override + public Optional getPrompt() { + return Optional.of(qName.getLocalName()); + } + }; + } + + @Override + public Optional getCommandDescription() { + return Optional.fromNullable(description); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/Command.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/Command.java new file mode 100644 index 0000000000..1435abd083 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/Command.java @@ -0,0 +1,34 @@ +/* + * 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.netconf.cli.commands; + +import com.google.common.base.Optional; +import org.opendaylight.controller.netconf.cli.commands.input.Input; +import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition; +import org.opendaylight.controller.netconf.cli.commands.output.Output; +import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.yangtools.yang.common.QName; + +/** + * Local command e.g. help or remote rpc e.g. get-config must conform to this interface + */ +public interface Command { + + Output invoke(Input inputArgs) throws CommandInvocationException; + + InputDefinition getInputDefinition(); + + OutputDefinition getOutputDefinition(); + + QName getCommandId(); + + Optional getCommandDescription(); + + ConsoleContext getConsoleContext(); +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandConstants.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandConstants.java new file mode 100644 index 0000000000..7159af5a25 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandConstants.java @@ -0,0 +1,29 @@ +/* + * 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.netconf.cli.commands; + +import java.net.URI; +import org.opendaylight.controller.netconf.cli.io.IOUtil; +import org.opendaylight.yangtools.yang.common.QName; + +public class CommandConstants { + + // Local command ids are defined here, this links the implementation to the rpc definition in yang + // Better way needs to be found to provide this link instead of hardcoded QNames (e.g. yang extension) + public static final QName HELP_QNAME = QName.create(URI.create("netconf:cli"), IOUtil.parseDate("2014-05-22"), "help"); + public static final QName CLOSE_QNAME = QName.create(HELP_QNAME, "close"); + public static final QName CONNECT_QNAME = QName.create(HELP_QNAME, "connect"); + public static final QName DISCONNECT_QNAME = QName.create(CONNECT_QNAME, "disconnect"); + + public static final QName ARG_HANDLER_EXT_QNAME = QName.create( + URI.create("urn:ietf:params:xml:ns:netconf:base:1.0:cli"), IOUtil.parseDate("2014-05-26"), + "argument-handler"); + + public static final QName NETCONF_BASE_QNAME = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "2011-06-01", + "netconf"); +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandDispatcher.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandDispatcher.java new file mode 100644 index 0000000000..ec7b5b4832 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandDispatcher.java @@ -0,0 +1,183 @@ +/* + * 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.netconf.cli.commands; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.opendaylight.controller.netconf.cli.NetconfDeviceConnectionHandler; +import org.opendaylight.controller.netconf.cli.NetconfDeviceConnectionManager; +import org.opendaylight.controller.netconf.cli.commands.local.Close; +import org.opendaylight.controller.netconf.cli.commands.local.Connect; +import org.opendaylight.controller.netconf.cli.commands.local.Disconnect; +import org.opendaylight.controller.netconf.cli.commands.local.Help; +import org.opendaylight.controller.netconf.cli.commands.remote.RemoteCommand; +import org.opendaylight.controller.netconf.cli.io.IOUtil; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; + +/** + * The registry of available commands local + remote. Created from schema contexts. + */ +public class CommandDispatcher { + + // TODO extract interface + + private final Map localCommands = Maps.newHashMap(); + private final Map nameToQNameLocal = Maps.newHashMap(); + + private final Map remoteCommands = Maps.newHashMap(); + private final Map nameToQNameRemote = Maps.newHashMap(); + + public synchronized Map getCommands() { + return Collections.unmodifiableMap(mergeCommands()); + } + + private Map mergeCommands() { + // TODO cache this merged map + return mergeMaps(remoteCommands, localCommands); + } + + private Map mergeCommandIds() { + // TODO cache this merged map + return mergeMaps(nameToQNameRemote, nameToQNameLocal); + } + + private Map mergeMaps(final Map remoteMap, final Map localMap) { + final Map mergedCommands = Maps.newHashMap(); + mergedCommands.putAll(remoteMap); + mergedCommands.putAll(localMap); + return mergedCommands; + } + + public synchronized Set getCommandIds() { + return mergeCommandIds().keySet(); + } + + public synchronized Set getRemoteCommandIds() { + return nameToQNameRemote.keySet(); + } + + public synchronized Optional getCommand(final String nameWithModule) { + final QName commandQName = mergeCommandIds().get(nameWithModule); + final Map qNameCommandMap = mergeCommands(); + if(commandQName == null || qNameCommandMap.containsKey(commandQName) == false) { + return Optional.absent(); + } + + return Optional.of(qNameCommandMap.get(commandQName)); + } + + public synchronized Optional getCommand(final QName qName) { + return Optional.fromNullable(mergeCommands().get(qName)); + } + + private static Optional getCommand(final Map commandNameMap, final Map commands, final String nameWithModule) { + final QName qName = commandNameMap.get(nameWithModule); + if(qName == null) + return Optional.absent(); + + final Command command = commands.get(qName); + if(command == null) { + return Optional.absent(); + } + + return Optional.of(command); + } + + public static final Collection BASE_NETCONF_SCHEMA_PATHS = Lists.newArrayList("/schema/remote/ietf-netconf.yang", + "/schema/common/netconf-cli-ext.yang", "/schema/common/ietf-inet-types.yang"); + + public synchronized void addRemoteCommands(final RpcImplementation rpcInvoker, final SchemaContext remoteSchema) { + this.addRemoteCommands(rpcInvoker, remoteSchema, parseSchema(BASE_NETCONF_SCHEMA_PATHS)); + } + + public synchronized void addRemoteCommands(final RpcImplementation rpcInvoker, final SchemaContext remoteSchema, final SchemaContext baseNetconfSchema) { + for (final SchemaContext context : Lists.newArrayList(remoteSchema, baseNetconfSchema)) { + for (final Module module : context.getModules()) { + for (final RpcDefinition rpcDefinition : module.getRpcs()) { + final Command command = RemoteCommand.fromRpc(rpcDefinition, rpcInvoker); + remoteCommands.put(rpcDefinition.getQName(), command); + nameToQNameRemote.put(getCommandName(rpcDefinition, module), rpcDefinition.getQName()); + } + } + } + } + + public synchronized void removeRemoteCommands() { + remoteCommands.clear(); + nameToQNameRemote.clear(); + } + + public static final Collection LOCAL_SCHEMA_PATHS = Lists.newArrayList("/schema/local/netconf-cli.yang", "/schema/common/netconf-cli-ext.yang", + "/schema/common/ietf-inet-types.yang"); + + public synchronized void addLocalCommands(final NetconfDeviceConnectionManager connectionManager, final SchemaContext localSchema, final Integer connectionTimeout) { + for (final Module module : localSchema.getModules()) { + for (final RpcDefinition rpcDefinition : module.getRpcs()) { + + // FIXME make local commands extensible + // e.g. by yang extension defining java class to be instantiated + // problem is with command specific resources + // e.g. Help would need command registry + final Command localCommand; + if (rpcDefinition.getQName().equals(CommandConstants.HELP_QNAME)) { + localCommand = Help.create(rpcDefinition, this); + } else if (rpcDefinition.getQName().equals(CommandConstants.CLOSE_QNAME)) { + localCommand = Close.create(rpcDefinition); + } else if (rpcDefinition.getQName().equals(CommandConstants.CONNECT_QNAME)) { + localCommand = Connect.create(rpcDefinition, connectionManager, connectionTimeout); + } else if (rpcDefinition.getQName().equals(CommandConstants.DISCONNECT_QNAME)) { + localCommand = Disconnect.create(rpcDefinition, connectionManager); + } else { + throw new IllegalStateException("No command implementation available for local command: " + rpcDefinition.getQName()); + } + + localCommands.put(localCommand.getCommandId(), localCommand); + nameToQNameLocal.put(getCommandName(rpcDefinition, module), localCommand.getCommandId()); + } + } + } + + private static String getCommandName(final RpcDefinition rpcDefinition, final Module module) { + return IOUtil.qNameToKeyString(rpcDefinition.getQName(), module.getName()); + } + + public static SchemaContext parseSchema(final Collection yangPath) { + final YangParserImpl yangParserImpl = new YangParserImpl(); + // TODO change deprecated method + final Set modules = yangParserImpl.parseYangModelsFromStreams(loadYangs(yangPath)); + return yangParserImpl.resolveSchemaContext(modules); + } + + private static List loadYangs(final Collection yangPaths) { + + return Lists.newArrayList(Collections2.transform(Lists.newArrayList(yangPaths), + new Function() { + @Override + public InputStream apply(final String input) { + final InputStream resourceAsStream = NetconfDeviceConnectionHandler.class.getResourceAsStream(input); + Preconditions.checkNotNull(resourceAsStream, "File %s was null", input); + return resourceAsStream; + } + })); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandInvocationException.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandInvocationException.java new file mode 100644 index 0000000000..e38f45f5ad --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/CommandInvocationException.java @@ -0,0 +1,28 @@ +/* + * 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.netconf.cli.commands; + +import org.opendaylight.yangtools.yang.common.QName; + +public class CommandInvocationException extends Exception { + + public CommandInvocationException(final QName qName, final Throwable cause) { + this("Command " + qName + " invocation failed: " + cause.getMessage(), cause); + } + + protected CommandInvocationException(final String message, final Throwable cause) { + super(message, cause); + } + + public static class CommandTimeoutException extends CommandInvocationException { + + public CommandTimeoutException(final QName qName, final Throwable e) { + super("Command " + qName + " timed out: " + e.getMessage(), e); + } + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/input/Input.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/input/Input.java new file mode 100644 index 0000000000..02173acf77 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/input/Input.java @@ -0,0 +1,53 @@ +/* + * 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.netconf.cli.commands.input; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; + +/** + * Input arguments for and rpc/command execution + */ +public class Input { + + private final List> args; + + private final Map> nameToArg = new HashMap>(); + + public Input(final List> args) { + // FIXME empty Input should be constructed from static factory method + if(args.isEmpty()) { + this.args = Collections.emptyList(); + return; + } + + final Node input = args.iterator().next(); + Preconditions + .checkArgument(input instanceof CompositeNode, "Input container has to be of type composite node."); + this.args = ((CompositeNode) input).getValue(); + + for (final Node arg : this.args) { + nameToArg.put(arg.getNodeType().getLocalName(), arg); + } + } + + public Node getArg(final String name) { + return nameToArg.get(name); + } + + public CompositeNode wrap(final QName rpcQName) { + return new CompositeNodeTOImpl(rpcQName, null, args); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/input/InputDefinition.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/input/InputDefinition.java new file mode 100644 index 0000000000..83e1b19391 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/input/InputDefinition.java @@ -0,0 +1,37 @@ +/* + * 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.netconf.cli.commands.input; + +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; + +/** + * The definition of input arguments represented by schema nodes parsed from + * yang rpc definition + */ +public class InputDefinition { + + private final ContainerSchemaNode inputContainer; + + public InputDefinition(final ContainerSchemaNode inputContainer) { + this.inputContainer = inputContainer; + } + + public static InputDefinition fromInput(final ContainerSchemaNode input) { + return new InputDefinition(input); + } + + public ContainerSchemaNode getInput() { + return inputContainer; + } + + // FIXME add empty as in output + public boolean isEmpty() { + return inputContainer == null; + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Close.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Close.java new file mode 100644 index 0000000000..c43432dbb6 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Close.java @@ -0,0 +1,41 @@ +/* + * 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.netconf.cli.commands.local; + +import org.opendaylight.controller.netconf.cli.commands.AbstractCommand; +import org.opendaylight.controller.netconf.cli.commands.Command; +import org.opendaylight.controller.netconf.cli.commands.CommandInvocationException; +import org.opendaylight.controller.netconf.cli.commands.input.Input; +import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition; +import org.opendaylight.controller.netconf.cli.commands.output.Output; +import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; + +/** + * Local command to shut down the cli + */ +public class Close extends AbstractCommand { + + public Close(final QName qName, final InputDefinition args, final OutputDefinition output, final String description) { + super(qName, args, output, description); + } + + @Override + public Output invoke(final Input inputArgs) throws CommandInvocationException { + // FIXME clean up, close session and then close + System.exit(0); + return null; + } + + public static Command create(final RpcDefinition rpcDefinition) { + return new Close(rpcDefinition.getQName(), getInputDefinition(rpcDefinition), + getOutputDefinition(rpcDefinition), rpcDefinition.getDescription()); + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Connect.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Connect.java new file mode 100644 index 0000000000..f702aa3805 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Connect.java @@ -0,0 +1,125 @@ +/* + * 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.netconf.cli.commands.local; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import io.netty.util.concurrent.GlobalEventExecutor; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Set; +import org.opendaylight.controller.netconf.cli.NetconfDeviceConnectionManager; +import org.opendaylight.controller.netconf.cli.commands.AbstractCommand; +import org.opendaylight.controller.netconf.cli.commands.Command; +import org.opendaylight.controller.netconf.cli.commands.input.Input; +import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition; +import org.opendaylight.controller.netconf.cli.commands.output.Output; +import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; +import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder; +import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; +import org.opendaylight.protocol.framework.NeverReconnectStrategy; +import org.opendaylight.protocol.framework.ReconnectStrategy; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.SimpleNode; +import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; +import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; + +/** + * Local command to connect to a remote device + */ +public class Connect extends AbstractCommand { + + private final NetconfDeviceConnectionManager connectManager; + private final Integer connectionTimeout; + + private Connect(final QName qName, final InputDefinition args, final OutputDefinition output, + final NetconfDeviceConnectionManager connectManager, final String description, final Integer connectionTimeout) { + super(qName, args, output, description); + this.connectManager = connectManager; + this.connectionTimeout = connectionTimeout; + } + + @Override + public Output invoke(final Input inputArgs) { + final NetconfClientConfigurationBuilder config = getConfig(inputArgs); + return invoke(config, getArgument(inputArgs, "address-name", String.class)); + } + + private Output invoke(final NetconfClientConfigurationBuilder config, final String addressName) { + final Set remoteCmds = connectManager.connectBlocking(addressName, config); + + final ArrayList> output = Lists.newArrayList(); + output.add(new SimpleNodeTOImpl<>(QName.create(getCommandId(), "status"), null, "Connection initiated")); + + for (final String cmdId : remoteCmds) { + output.add(new SimpleNodeTOImpl<>(QName.create(getCommandId(), "remote-commands"), null, cmdId)); + } + + return new Output(new CompositeNodeTOImpl(getCommandId(), null, output)); + } + + private NetconfClientConfigurationBuilder getConfig(final Input inputArgs) { + + final ReconnectStrategy strategy = getReconnectStrategy(); + + final String address = getArgument(inputArgs, "address-name", String.class); + final Integer port = getArgument(inputArgs, "address-port", Integer.class); + final String username = getArgument(inputArgs, "user-name", String.class); + final String passwd = getArgument(inputArgs, "user-password", String.class); + + final InetSocketAddress inetAddress; + try { + inetAddress = new InetSocketAddress(InetAddress.getByName(address), port); + } catch (final UnknownHostException e) { + throw new IllegalArgumentException("Unable to use address: " + address, e); + } + + return NetconfClientConfigurationBuilder.create().withAddress(inetAddress) + .withConnectionTimeoutMillis(connectionTimeout) + .withReconnectStrategy(strategy) + .withAuthHandler(new LoginPassword(username, passwd)) + .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH); + } + + private Optional getArgumentOpt(final Input inputArgs, final String argName, final Class type) { + final QName argQName = QName.create(getCommandId(), argName); + final Node argumentNode = inputArgs.getArg(argName); + if (argumentNode == null) { + return Optional.absent(); + } + Preconditions.checkArgument(argumentNode instanceof SimpleNode, "Only simple type argument supported, %s", + argQName); + + final Object value = argumentNode.getValue(); + Preconditions.checkArgument(type.isInstance(value), "Unexpected instance type: %s for argument: %s", + value.getClass(), argQName); + return Optional.of(type.cast(value)); + } + + private T getArgument(final Input inputArgs, final String argName, final Class type) { + final Optional argumentOpt = getArgumentOpt(inputArgs, argName, type); + Preconditions.checkState(argumentOpt.isPresent(), "Argument: %s is missing but is required", argName); + return argumentOpt.get(); + } + + public static ReconnectStrategy getReconnectStrategy() { + // FIXME move to args either start-up args or input nodes for connect or both + return new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 1000); + } + + public static Command create(final RpcDefinition rpcDefinition, final NetconfDeviceConnectionManager connectManager, final Integer connectionTimeout) { + return new Connect(rpcDefinition.getQName(), getInputDefinition(rpcDefinition), + getOutputDefinition(rpcDefinition), connectManager, rpcDefinition.getDescription(), connectionTimeout); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Disconnect.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Disconnect.java new file mode 100644 index 0000000000..723e484c9c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Disconnect.java @@ -0,0 +1,52 @@ +/* + * 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.netconf.cli.commands.local; + +import com.google.common.collect.Lists; +import org.opendaylight.controller.netconf.cli.NetconfDeviceConnectionManager; +import org.opendaylight.controller.netconf.cli.commands.AbstractCommand; +import org.opendaylight.controller.netconf.cli.commands.Command; +import org.opendaylight.controller.netconf.cli.commands.input.Input; +import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition; +import org.opendaylight.controller.netconf.cli.commands.output.Output; +import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; +import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; + +/** + * Local disconnect command + */ +public class Disconnect extends AbstractCommand { + + private final NetconfDeviceConnectionManager connectionManager; + + public Disconnect(final QName qName, final InputDefinition inputDefinition, + final OutputDefinition outputDefinition, final NetconfDeviceConnectionManager connectionManager, + final String description) { + super(qName, inputDefinition, outputDefinition, description); + this.connectionManager = connectionManager; + } + + @Override + public Output invoke(final Input inputArgs) { + connectionManager.disconnect(); + + return new Output(new CompositeNodeTOImpl(getCommandId(), null, + Lists.> newArrayList(new SimpleNodeTOImpl<>(new QName(getCommandId(), "status"), null, + "Connection disconnected")))); + } + + public static Command create(final RpcDefinition rpcDefinition, + final NetconfDeviceConnectionManager commandDispatcher) { + return new Disconnect(rpcDefinition.getQName(), getInputDefinition(rpcDefinition), + getOutputDefinition(rpcDefinition), commandDispatcher, rpcDefinition.getDescription()); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Help.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Help.java new file mode 100644 index 0000000000..18164696a2 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/local/Help.java @@ -0,0 +1,63 @@ +/* + * 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.netconf.cli.commands.local; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.List; +import org.opendaylight.controller.netconf.cli.commands.AbstractCommand; +import org.opendaylight.controller.netconf.cli.commands.Command; +import org.opendaylight.controller.netconf.cli.commands.CommandDispatcher; +import org.opendaylight.controller.netconf.cli.commands.input.Input; +import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition; +import org.opendaylight.controller.netconf.cli.commands.output.Output; +import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.NodeFactory; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; + +/** + * Local Help command. Displays all commands with description. + */ +public class Help extends AbstractCommand { + + private final CommandDispatcher commandDispatcher; + + public Help(final QName qName, final InputDefinition argsDefinition, final OutputDefinition output, final String description, final CommandDispatcher commandDispatcher) { + super(qName, argsDefinition, output, description); + this.commandDispatcher = commandDispatcher; + } + + @Override + public Output invoke(final Input inputArgs) { + final ArrayList> value = Lists.newArrayList(); + + for (final String id : commandDispatcher.getCommandIds()) { + final Optional cmd = commandDispatcher.getCommand(id); + Preconditions.checkState(cmd.isPresent(), "Command %s has to be present in command dispatcher", id); + final Optional description = cmd.get().getCommandDescription(); + final List> nameAndDescription = Lists.newArrayList(); + nameAndDescription.add(NodeFactory.createImmutableSimpleNode(QName.create(getCommandId(), "id"), null, id)); + if(description.isPresent()) { + nameAndDescription.add(NodeFactory.createImmutableSimpleNode(QName.create(getCommandId(), "description"), null, description.get())); + } + value.add(ImmutableCompositeNode.create(QName.create(getCommandId(), "commands"), nameAndDescription)); + } + + return new Output(new CompositeNodeTOImpl(getCommandId(), null, value)); + } + + public static Command create(final RpcDefinition rpcDefinition, final CommandDispatcher commandDispatcher) { + return new Help(rpcDefinition.getQName(), getInputDefinition(rpcDefinition), getOutputDefinition(rpcDefinition), rpcDefinition.getDescription(), commandDispatcher); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/output/Output.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/output/Output.java new file mode 100644 index 0000000000..c366c8969c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/output/Output.java @@ -0,0 +1,61 @@ +/* + * 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.netconf.cli.commands.output; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.util.List; +import java.util.Map; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +/** + * Output values for and rpc/command execution + */ +public class Output { + + private final CompositeNode output; + + public Output(final CompositeNode output) { + this.output = output; + } + + public Map>> unwrap(final OutputDefinition outputDefinition) { + Preconditions.checkArgument(outputDefinition.isEmpty() == false); + + final Map mappedSchemaNodes = mapOutput(outputDefinition); + final Map>> mappedNodesToSchema = Maps.newHashMap(); + + for (final Node node : output.getValue()) { + final DataSchemaNode schemaNode = mappedSchemaNodes.get(node.getKey().withoutRevision()); + final List> list = mappedNodesToSchema.get(schemaNode) == null ? Lists.> newArrayList() + : mappedNodesToSchema.get(schemaNode); + list.add(node); + mappedNodesToSchema.put(schemaNode, list); + } + + return mappedNodesToSchema; + } + + public CompositeNode getOutput() { + return output; + } + + private Map mapOutput(final OutputDefinition outputDefinition) { + final Map mapped = Maps.newHashMap(); + for (final DataSchemaNode dataSchemaNode : outputDefinition) { + // without revision since data QNames come without revision + mapped.put(dataSchemaNode.getQName().withoutRevision(), dataSchemaNode); + } + + return mapped; + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/output/OutputDefinition.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/output/OutputDefinition.java new file mode 100644 index 0000000000..66d0d4da46 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/output/OutputDefinition.java @@ -0,0 +1,47 @@ +/* + * 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.netconf.cli.commands.output; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +/** + * The definition of output elements represented by schema nodes parsed from yang rpc definition + */ +public class OutputDefinition implements Iterable { + + public static final OutputDefinition EMPTY_OUTPUT = new OutputDefinition(Collections.emptySet()); + private final Set childNodes; + + public OutputDefinition(final Set childNodes) { + this.childNodes = childNodes; + } + + @Override + public Iterator iterator() { + return childNodes.iterator(); + } + + public static OutputDefinition fromOutput(final ContainerSchemaNode output) { + Preconditions.checkNotNull(output); + return new OutputDefinition(output.getChildNodes()); + } + + public static OutputDefinition empty() { + return EMPTY_OUTPUT; + } + + public boolean isEmpty() { + return this == EMPTY_OUTPUT; + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/remote/RemoteCommand.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/remote/RemoteCommand.java new file mode 100644 index 0000000000..05b9e85e79 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/commands/remote/RemoteCommand.java @@ -0,0 +1,65 @@ +/* + * 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.netconf.cli.commands.remote; + +import com.google.common.util.concurrent.ListenableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.opendaylight.controller.netconf.cli.commands.AbstractCommand; +import org.opendaylight.controller.netconf.cli.commands.Command; +import org.opendaylight.controller.netconf.cli.commands.CommandInvocationException; +import org.opendaylight.controller.netconf.cli.commands.input.Input; +import org.opendaylight.controller.netconf.cli.commands.input.InputDefinition; +import org.opendaylight.controller.netconf.cli.commands.output.Output; +import org.opendaylight.controller.netconf.cli.commands.output.OutputDefinition; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; + +/** + * Generic remote command implementation that sends the rpc xml to the remote device and waits for response + * Waiting is limited with TIMEOUT + */ +public class RemoteCommand extends AbstractCommand { + + // TODO make this configurable + private static final long DEFAULT_TIMEOUT = 10000; + private static final TimeUnit DEFAULT_TIMEOUT_UNIT = TimeUnit.MILLISECONDS; + private final RpcImplementation rpc; + + public RemoteCommand(final QName qName, final InputDefinition args, final OutputDefinition output, final String description, final RpcImplementation rpc) { + super(qName, args, output, description); + this.rpc = rpc; + } + + @Override + public Output invoke(final Input inputArgs) throws CommandInvocationException { + final ListenableFuture> invokeRpc = rpc.invokeRpc(getCommandId(), inputArgs.wrap(getCommandId())); + try { + return new Output(invokeRpc.get(DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_UNIT).getResult()); + } catch (final ExecutionException e) { + throw new CommandInvocationException(getCommandId(), e); + } catch (final TimeoutException e) { + // Request timed out, cancel request + invokeRpc.cancel(true); + throw new CommandInvocationException.CommandTimeoutException(getCommandId(), e); + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static Command fromRpc(final RpcDefinition rpcDefinition, final RpcImplementation rpcInvoker) { + final InputDefinition args = getInputDefinition(rpcDefinition); + final OutputDefinition retVal = getOutputDefinition(rpcDefinition); + + return new RemoteCommand(rpcDefinition.getQName(), args, retVal, rpcDefinition.getDescription(), rpcInvoker); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/BaseConsoleContext.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/BaseConsoleContext.java new file mode 100644 index 0000000000..26e46d39f2 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/BaseConsoleContext.java @@ -0,0 +1,51 @@ +/* + * 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.netconf.cli.io; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import jline.console.completer.AggregateCompleter; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +public class BaseConsoleContext implements ConsoleContext { + + private static final Completer SKIP_COMPLETER = new StringsCompleter(IOUtil.SKIP); + + private final T dataSchemaNode; + + public BaseConsoleContext(final T dataSchemaNode) { + Preconditions.checkNotNull(dataSchemaNode); + this.dataSchemaNode = dataSchemaNode; + } + + @Override + public Completer getCompleter() { + final ArrayList completers = Lists.newArrayList(SKIP_COMPLETER); + completers.addAll(getAdditionalCompleters()); + return new AggregateCompleter(completers); + } + + protected List getAdditionalCompleters() { + return Collections.emptyList(); + } + + @Override + public Optional getPrompt() { + return Optional.of(dataSchemaNode.getQName().getLocalName()); + } + + protected T getDataSchemaNode() { + return dataSchemaNode; + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleContext.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleContext.java new file mode 100644 index 0000000000..f4ebfca8d9 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleContext.java @@ -0,0 +1,22 @@ +/* + * 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.netconf.cli.io; + +import com.google.common.base.Optional; +import jline.console.completer.Completer; + +/** + * Context to be set in the IO. Different prompts + completers are required in different contexts of the CLI. + */ +public interface ConsoleContext { + + Completer getCompleter(); + + Optional getPrompt(); + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleIO.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleIO.java new file mode 100644 index 0000000000..6bffeaca6f --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleIO.java @@ -0,0 +1,37 @@ +/* + * 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.netconf.cli.io; + +import java.io.IOException; + +/** + * Definition of IO interface + */ +public interface ConsoleIO { + + String read() throws IOException; + + String read(Character mask) throws IOException; + + void write(CharSequence data) throws IOException; + + void writeLn(CharSequence data) throws IOException; + + void formatLn(String format, Object... args) throws IOException; + + void enterContext(ConsoleContext consoleContext); + + void enterRootContext(ConsoleContext consoleContext); + + void leaveContext(); + + void leaveRootContext(); + + void complete(); + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleIOImpl.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleIOImpl.java new file mode 100644 index 0000000000..5b7374a698 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/ConsoleIOImpl.java @@ -0,0 +1,157 @@ +/* + * 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.netconf.cli.io; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.PATH_SEPARATOR; +import static org.opendaylight.controller.netconf.cli.io.IOUtil.PROMPT_SUFIX; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; +import jline.console.ConsoleReader; +import jline.console.completer.Completer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Jline based IO implementation + */ +public class ConsoleIOImpl implements ConsoleIO { + + private static final Logger LOG = LoggerFactory.getLogger(ConsoleIOImpl.class); + + private final ConsoleReader console; + private final Deque contexts = new ArrayDeque<>(); + + public ConsoleIOImpl() throws IOException { + console = new ConsoleReader(); + console.setHandleUserInterrupt(true); + console.setPaginationEnabled(true); + console.setHistoryEnabled(true); + + // TODO trifferedActions not supported by jline in current version + // https://github.com/jline/jline2/issues/149 + console.addTriggeredAction('?', new QuestionMarkActionListener()); + } + + @Override + public String read() throws IOException { + return console.readLine().trim(); + } + + @Override + public String read(final Character mask) throws IOException { + return console.readLine(mask).trim(); + } + + @Override + public void write(final CharSequence data) throws IOException { + console.print(data); + console.flush(); + } + + @Override + public void writeLn(final CharSequence data) throws IOException { + console.println(data); + console.flush(); + } + + @Override + public void formatLn(final String format, final Object... args) throws IOException { + console.println(String.format(format, args)); + console.flush(); + } + + @Override + public void enterContext(final ConsoleContext consoleContext) { + contexts.push(consoleContext); + enterCtx(consoleContext); + } + + + @Override + public void enterRootContext(final ConsoleContext consoleContext) { + contexts.addLast(consoleContext); + enterCtx(consoleContext); + } + + private void enterCtx(final ConsoleContext consoleContext) { + setCompleter(consoleContext.getCompleter()); + console.setPrompt(buildPrompt()); + } + + @Override + public void leaveContext() { + contexts.pollFirst(); + leaveCtx(); + } + + @Override + public void leaveRootContext() { + contexts.pollLast(); + leaveCtx(); + } + + private void leaveCtx() { + console.setPrompt(buildPrompt()); + if (contexts.peek() != null) { + setCompleter(contexts.peek().getCompleter()); + } + } + + protected String buildPrompt() { + final StringBuilder newPrompt = new StringBuilder(); + + final Iterator descendingIterator = contexts.descendingIterator(); + while (descendingIterator.hasNext()) { + final ConsoleContext consoleContext = descendingIterator.next(); + final Optional promptPart = consoleContext.getPrompt(); + if (promptPart.isPresent()) { + newPrompt.append(PATH_SEPARATOR); + newPrompt.append(promptPart.get()); + } + } + if (newPrompt.length() ==0) { + newPrompt.append(PATH_SEPARATOR); + } + + newPrompt.append(PROMPT_SUFIX); + + return newPrompt.toString(); + } + + private void setCompleter(final Completer newCompleter) { + for (final Completer concreteCompleter : console.getCompleters()) { + console.removeCompleter(concreteCompleter); + } + console.addCompleter(newCompleter); + } + + private class QuestionMarkActionListener implements ActionListener { + @Override + public void actionPerformed(final ActionEvent e) { + ConsoleIOImpl.this.complete(); + } + } + + public void complete() { + final ArrayList candidates = Lists.newArrayList(); + contexts.peek().getCompleter().complete("", 0, candidates); + try { + console.getCompletionHandler().complete(console, candidates, 0); + } catch (final IOException ex) { + throw new IllegalStateException("Unable to write to output", ex); + } + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/IOUtil.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/IOUtil.java new file mode 100644 index 0000000000..1817cddad6 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/io/IOUtil.java @@ -0,0 +1,84 @@ +/* + * 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.netconf.cli.io; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; + +public class IOUtil { + + public static final String SKIP = "skip"; + public static final String PROMPT_SUFIX = ">"; + public static final String PATH_SEPARATOR = "/"; + + private IOUtil() { + } + + public static boolean isQName(final String qName) { + final Matcher matcher = patternNew.matcher(qName); + return matcher.matches(); + } + + public static Date parseDate(final String revision) { + final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + try { + return formatter.parse(revision); + } catch (final ParseException e) { + throw new IllegalArgumentException("Date not valid", e); + } + } + + public static String listType(final SchemaNode schemaNode) { + if (schemaNode instanceof LeafListSchemaNode) { + return "Leaf-list"; + } else if (schemaNode instanceof ListSchemaNode) { + return "List"; + } else if (schemaNode instanceof LeafSchemaNode) { + return "Leaf"; + } + // FIXME throw exception on unexpected state, not null/emptyString + return ""; + } + + public static String qNameToKeyString(final QName qName, final String moduleName) { + return String.format("%s(%s)", qName.getLocalName(), moduleName); + } + + // TODO test and check regex + review format of string for QName + final static Pattern patternNew = Pattern.compile("([^\\)]+)\\(([^\\)]+)\\)"); + + public static QName qNameFromKeyString(final String qName, final Map mappedModules) + throws ReadingException { + final Matcher matcher = patternNew.matcher(qName); + if (!matcher.matches()) { + final String message = String.format("QName in wrong format: %s should be: %s", qName, patternNew); + throw new ReadingException(message); + } + final QName base = mappedModules.get(matcher.group(2)); + if (base == null) { + final String message = String.format("Module %s cannot be found", matcher.group(2)); + throw new ReadingException(message); + } + return QName.create(base, matcher.group(1)); + } + + public static boolean isSkipInput(final String rawValue) { + return rawValue.equals(SKIP); + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/AbstractReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/AbstractReader.java new file mode 100644 index 0000000000..6131eef4bc --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/AbstractReader.java @@ -0,0 +1,118 @@ +/* + * 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.netconf.cli.reader; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import jline.console.completer.Completer; +import jline.console.completer.NullCompleter; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition; + +public abstract class AbstractReader implements Reader { + + public static final NullContext NULL_CONTEXT = new NullContext(); + + // TODO make console private add protected getter + protected ConsoleIO console; + private final SchemaContext context; + private boolean readConfigNode = false; + + public AbstractReader(final ConsoleIO console, final SchemaContext context) { + this.console = console; + this.context = context; + } + + public AbstractReader(final ConsoleIO console, final SchemaContext context, final boolean readConfigNode) { + this(console, context); + this.readConfigNode = readConfigNode; + } + + protected SchemaContext getSchemaContext() { + return context; + } + + protected ConsoleIO getConsole() { + return console; + } + + protected boolean getReadConfigNode() { + return readConfigNode; + } + + @Override + public List> read(final T schemaNode) throws ReadingException { + if (isReadingWanted(schemaNode)) { + final ConsoleContext ctx = getContext(schemaNode); + console.enterContext(ctx); + try { + return readWithContext(schemaNode); + } catch (final IOException e) { + throw new ReadingException("Unable to read data from input for " + schemaNode.getQName(), e); + } finally { + console.leaveContext(); + } + } + return Collections.emptyList(); + } + + private boolean isReadingWanted(final DataSchemaNode node) { + if (readConfigNode && !node.isConfiguration()) { + return false; + } + return true; + } + + // TODO javadoc + + protected abstract List> readWithContext(T schemaNode) throws IOException, ReadingException; + + protected abstract ConsoleContext getContext(T schemaNode); + + protected Optional getDefaultValue(final T schemaNode) { + String defaultValue = null; + if (schemaNode instanceof LeafSchemaNode) { + defaultValue = ((LeafSchemaNode) schemaNode).getDefault(); + } else if (schemaNode instanceof ChoiceNode) { + defaultValue = ((ChoiceNode) schemaNode).getDefaultCase(); + } + + return Optional.fromNullable(defaultValue); + } + + protected boolean isEmptyInput(final String rawValue) { + return Strings.isNullOrEmpty(rawValue); + } + + protected static boolean isEmptyType(final TypeDefinition type) { + return type instanceof EmptyTypeDefinition; + } + + private static class NullContext implements ConsoleContext { + @Override + public Completer getCompleter() { + return new NullCompleter(); + } + + @Override + public Optional getPrompt() { + return Optional.absent(); + } + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/GenericListEntryReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/GenericListEntryReader.java new file mode 100644 index 0000000000..a30b182a8c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/GenericListEntryReader.java @@ -0,0 +1,17 @@ +/* + * 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.netconf.cli.reader; + +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +/** + * marker interface to mark reader which can be used with GenericListReader + */ +public interface GenericListEntryReader extends Reader { + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/Reader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/Reader.java new file mode 100644 index 0000000000..9f27b8f273 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/Reader.java @@ -0,0 +1,21 @@ +/* + * 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.netconf.cli.reader; + +import java.util.List; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +/** + * Generic provider(reader) of input arguments for commands + */ +public interface Reader { + + List> read(T schemaNode) throws ReadingException; + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/ReadingException.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/ReadingException.java new file mode 100644 index 0000000000..81915fc148 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/ReadingException.java @@ -0,0 +1,31 @@ +/* + * 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.netconf.cli.reader; + +public class ReadingException extends Exception { + + private static final long serialVersionUID = -298382323286156591L; + + public ReadingException(final String msg, final Exception e) { + super(msg, e); + } + + public ReadingException(final String msg) { + super(msg); + } + + public static class IncorrectValueException extends ReadingException { + + private static final long serialVersionUID = 164168437058431592L; + + public IncorrectValueException(final String msg) { + super(msg); + } + + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/ConfigReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/ConfigReader.java new file mode 100644 index 0000000000..95fc098c76 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/ConfigReader.java @@ -0,0 +1,203 @@ +/* + * 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.netconf.cli.reader.custom; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import jline.console.completer.Completer; +import org.opendaylight.controller.netconf.cli.CommandArgHandlerRegistry; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.io.IOUtil; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * Custom reader implementation for filter elements in get/get-config rpcs. This + * reader overrides the default anyxml reader and reads filter as a schema path. + */ +public class ConfigReader extends AbstractReader { + + public static final String SEPARATOR = "/"; + + private final CommandArgHandlerRegistry commandArgHandlerRegistry; + private final Map mappedModules; + private final Map mappedModulesNamespace; + + public ConfigReader(final ConsoleIO console, final SchemaContext remoteSchemaContext, + final CommandArgHandlerRegistry commandArgHandlerRegistry) { + super(console, remoteSchemaContext); + this.commandArgHandlerRegistry = commandArgHandlerRegistry; + + mappedModules = Maps.newHashMap(); + mappedModulesNamespace = Maps.newHashMap(); + for (final Module module : remoteSchemaContext.getModules()) { + final QName moduleQName = QName.create(module.getNamespace(), module.getRevision(), module.getName()); + mappedModules.put(moduleQName.getLocalName(), moduleQName); + mappedModulesNamespace.put(moduleQName.getNamespace(), moduleQName); + } + } + + // FIXME refactor + unite common code with FilterReader + + @Override + protected List> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException { + console.writeLn("Config " + schemaNode.getQName().getLocalName()); + console.writeLn("Submit path of the data to edit. Use TAB for autocomplete"); + + final String rawValue = console.read(); + + // FIXME isSkip check should be somewhere in abstractReader + if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) { + return Collections.emptyList(); + } + + final List filterPartsQNames = Lists.newArrayList(); + + for (final String part : rawValue.split(SEPARATOR)) { + final QName qName = IOUtil.qNameFromKeyString(part, mappedModules); + filterPartsQNames.add(qName); + } + + List> previous = readInnerNode(rawValue); + + for (final QName qName : Lists.reverse(filterPartsQNames).subList(1, filterPartsQNames.size())) { + previous = Collections.> singletonList(new CompositeNodeTOImpl(qName, null, + previous == null ? Collections.> emptyList() : previous)); + } + + final Node newNode = previous == null ? null + : new CompositeNodeTOImpl(schemaNode.getQName(), null, previous); + + return Collections.> singletonList(newNode); + } + + private List> readInnerNode(final String pathString) throws ReadingException { + final Optional schema = getCurrentNode(getSchemaContext(), pathString); + Preconditions.checkState(schema.isPresent(), "Unable to find schema for %s", pathString); + return commandArgHandlerRegistry.getGenericReader(getSchemaContext(), true).read(schema.get()); + } + + @Override + protected ConsoleContext getContext(final DataSchemaNode schemaNode) { + return new FilterConsoleContext(schemaNode, getSchemaContext()); + } + + private final class FilterConsoleContext extends BaseConsoleContext { + + private final SchemaContext remoteSchemaContext; + + public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) { + super(schemaNode); + this.remoteSchemaContext = remoteSchemaContext; + } + + @Override + protected List getAdditionalCompleters() { + return Collections. singletonList(new FilterCompleter(remoteSchemaContext)); + } + } + + private final class FilterCompleter implements Completer { + + private final SchemaContext remoteSchemaContext; + + public FilterCompleter(final SchemaContext remoteSchemaContext) { + this.remoteSchemaContext = remoteSchemaContext; + } + + @Override + public int complete(final String buffer, final int cursor, final List candidates) { + final int idx = buffer.lastIndexOf(SEPARATOR); + + final Optional currentNode = getCurrentNode(remoteSchemaContext, buffer); + if (currentNode.isPresent() && currentNode.get() instanceof DataNodeContainer) { + final Collection childNodes = ((DataNodeContainer) currentNode.get()).getChildNodes(); + final Collection transformed = Collections2.transform(childNodes, + new Function() { + @Override + public String apply(final DataSchemaNode input) { + return IOUtil.qNameToKeyString(input.getQName(), + mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName()); + } + }); + + fillCandidates(buffer.substring(idx + 1), candidates, transformed); + } + + return idx == -1 ? 0 : idx + 1; + } + + private void fillCandidates(final String buffer, final List candidates, + final Collection transformed) { + final SortedSet strings = new TreeSet<>(transformed); + + if (buffer == null) { + candidates.addAll(strings); + } else { + for (final String match : strings.tailSet(buffer)) { + if (!match.startsWith(buffer)) { + break; + } + candidates.add(match); + } + } + + if (candidates.size() == 1) { + candidates.set(0, candidates.get(0) + SEPARATOR); + } + } + + } + + private Optional getCurrentNode(DataSchemaNode parent, final String buffer) { + for (final String part : buffer.split(SEPARATOR)) { + if (IOUtil.isQName(part) == false) { + return Optional.of(parent); + } + + final QName qName; + try { + qName = IOUtil.qNameFromKeyString(part, mappedModules); + } catch (final ReadingException e) { + return Optional.of(parent); + } + if (parent instanceof DataNodeContainer) { + parent = ((DataNodeContainer) parent).getDataChildByName(qName); + } else { + // This should check if we are at the end of buffer ? + return Optional.of(parent); + } + } + return Optional.of(parent); + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/EditContentReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/EditContentReader.java new file mode 100644 index 0000000000..af43d37909 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/EditContentReader.java @@ -0,0 +1,43 @@ +/* + * 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.netconf.cli.reader.custom; + +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.util.List; +import org.opendaylight.controller.netconf.cli.CommandArgHandlerRegistry; +import org.opendaylight.controller.netconf.cli.commands.CommandConstants; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.controller.netconf.cli.reader.impl.ChoiceReader; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class EditContentReader extends ChoiceReader { + + public static final QName EDIT_CONTENT_QNAME = QName.create(CommandConstants.NETCONF_BASE_QNAME, "edit-content"); + public static final QName CONFIG_QNAME = QName.create(EDIT_CONTENT_QNAME, "config"); + + // FIXME this could be removed if feature/if-feature are supported + + public EditContentReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, final SchemaContext schemaContext) { + super(console, argumentHandlerRegistry, schemaContext); + } + + @Override + public List> readWithContext(final ChoiceNode choiceNode) throws IOException, ReadingException { + Preconditions.checkState(choiceNode.getQName().equals(EDIT_CONTENT_QNAME), "Unexpected choice %s, expected %s", choiceNode, EDIT_CONTENT_QNAME); + final ChoiceCaseNode selectedCase = choiceNode.getCaseNodeByName(CONFIG_QNAME); + Preconditions.checkNotNull(selectedCase, "Unexpected choice %s, expected %s that contains %s", choiceNode, EDIT_CONTENT_QNAME, CONFIG_QNAME); + return readSelectedCase(selectedCase); + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/FilterReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/FilterReader.java new file mode 100644 index 0000000000..7b37f695ba --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/FilterReader.java @@ -0,0 +1,218 @@ +/* + * 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.netconf.cli.reader.custom; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import jline.console.completer.Completer; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.io.IOUtil; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Custom reader implementation for filter elements in get/get-config rpcs. This + * reader overrides the default anyxml reader and reads filter as a schema path. + */ +public class FilterReader extends AbstractReader { + + private static final Logger LOG = LoggerFactory.getLogger(FilterReader.class); + + public static final String SEPARATOR = "/"; + + private final Map mappedModules; + private final Map mappedModulesNamespace; + + public FilterReader(final ConsoleIO console, final SchemaContext remoteSchemaContext) { + super(console, remoteSchemaContext); + + mappedModules = Maps.newHashMap(); + mappedModulesNamespace = Maps.newHashMap(); + for (final Module module : remoteSchemaContext.getModules()) { + final QName moduleQName = QName.create(module.getNamespace(), module.getRevision(), module.getName()); + mappedModules.put(moduleQName.getLocalName(), moduleQName); + mappedModulesNamespace.put(moduleQName.getNamespace(), moduleQName); + } + } + + // FIXME refactor + + public static final QName FILTER_TYPE_QNAME = QName.create("urn:ietf:params:xml:ns:netconf:base:1.0", "2011-06-01", + "type"); + public static final String FILTER_TYPE_VALUE_DEFAULT = "subtree"; + + @Override + protected List> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException { + boolean redSuccessfuly = false; + Node newNode = null; + do { + console.writeLn("Filter " + schemaNode.getQName().getLocalName()); + console.writeLn("Submit path of the data to retrieve. Use TAB for autocomplete"); + + final String rawValue = console.read(); + + // FIXME skip should be somewhere in abstractReader + if (isSkipInput(rawValue) || Strings.isNullOrEmpty(rawValue)) { + return Collections.emptyList(); + } + + final List filterPartsQNames = Lists.newArrayList(); + + try { + for (final String part : rawValue.split(SEPARATOR)) { + final QName qName = IOUtil.qNameFromKeyString(part, mappedModules); + filterPartsQNames.add(qName); + } + + Node previous = null; + + for (final QName qName : Lists.reverse(filterPartsQNames)) { + previous = new CompositeNodeTOImpl(qName, null, + previous == null ? Collections.> emptyList() + : Collections.> singletonList(previous)); + } + + final Map attributes = Collections.singletonMap(FILTER_TYPE_QNAME, + FILTER_TYPE_VALUE_DEFAULT); + newNode = previous == null ? null : ImmutableCompositeNode.create(schemaNode.getQName(), attributes, + Collections.> singletonList(previous)); + redSuccessfuly = true; + } catch (final ReadingException e) { + final String message = "Specified filter path isn't correct."; + LOG.error(message, e); + console.writeLn(message); + } + } while (!redSuccessfuly); + return Collections.> singletonList(newNode); + } + + @Override + protected ConsoleContext getContext(final DataSchemaNode schemaNode) { + return new FilterConsoleContext(schemaNode, getSchemaContext()); + } + + private final class FilterConsoleContext extends BaseConsoleContext { + + private final SchemaContext remoteSchemaContext; + + public FilterConsoleContext(final DataSchemaNode schemaNode, final SchemaContext remoteSchemaContext) { + super(schemaNode); + this.remoteSchemaContext = remoteSchemaContext; + } + + @Override + protected List getAdditionalCompleters() { + return Collections. singletonList(new FilterCompleter(remoteSchemaContext)); + } + + } + + private final class FilterCompleter implements Completer { + + private final SchemaContext remoteSchemaContext; + + // TODO add skip to filter completer, better soulution would be to add + // SKIP completer before context completer if possible + + public FilterCompleter(final SchemaContext remoteSchemaContext) { + this.remoteSchemaContext = remoteSchemaContext; + } + + @Override + public int complete(final String buffer, final int cursor, final List candidates) { + final int idx = buffer.lastIndexOf(SEPARATOR); + + final Optional currentNode = getCurrentNode(remoteSchemaContext, buffer); + if (currentNode.isPresent()) { + + final Collection transformed = Collections2.transform(currentNode.get().getChildNodes(), + new Function() { + @Override + public String apply(final DataSchemaNode input) { + return IOUtil.qNameToKeyString(input.getQName(), + mappedModulesNamespace.get(input.getQName().getNamespace()).getLocalName()); + } + }); + + fillCandidates(buffer.substring(idx + 1), candidates, transformed); + } + + return idx == -1 ? 0 : idx + 1; + } + + private void fillCandidates(final String buffer, final List candidates, + final Collection transformed) { + final SortedSet strings = new TreeSet<>(transformed); + + if (buffer == null) { + candidates.addAll(strings); + } else { + for (final String match : strings.tailSet(buffer)) { + if (!match.startsWith(buffer)) { + break; + } + candidates.add(match); + } + } + + if (candidates.size() == 1) { + candidates.set(0, candidates.get(0) + SEPARATOR); + } + } + + private Optional getCurrentNode(DataNodeContainer parent, final String buffer) { + for (final String part : buffer.split(SEPARATOR)) { + if (!IOUtil.isQName(part)) { + return Optional.of(parent); + } + + QName qName; + try { + qName = IOUtil.qNameFromKeyString(part, mappedModules); + } catch (final ReadingException e) { + return Optional.of(parent); + } + + final DataSchemaNode dataChildByName = parent.getDataChildByName(qName); + if (dataChildByName instanceof DataNodeContainer) { + parent = (DataNodeContainer) dataChildByName; + } else { + return Optional.absent(); + } + } + return Optional.of(parent); + } + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/PasswordReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/PasswordReader.java new file mode 100644 index 0000000000..4804455c60 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/custom/PasswordReader.java @@ -0,0 +1,51 @@ +/* + * 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.netconf.cli.reader.custom; + +import com.google.common.base.Preconditions; +import java.io.IOException; +import jline.console.completer.Completer; +import jline.console.completer.NullCompleter; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.impl.BasicDataHolderReader; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; + +public class PasswordReader extends BasicDataHolderReader { + + private static final char PASSWORD_MASK = '*'; + + public PasswordReader(final ConsoleIO console, final SchemaContext schemaContext) { + super(console, schemaContext); + } + + @Override + protected ConsoleContext getContext(final DataSchemaNode schemaNode) { + return new BaseConsoleContext(schemaNode) { + @Override + public Completer getCompleter() { + return new NullCompleter(); + } + }; + } + + @Override + protected TypeDefinition getType(final DataSchemaNode schemaNode) { + Preconditions.checkArgument(schemaNode instanceof LeafSchemaNode); + return ((LeafSchemaNode)schemaNode).getType(); + } + + @Override + protected String readValue() throws IOException { + return console.read(PASSWORD_MASK); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/AnyXmlReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/AnyXmlReader.java new file mode 100644 index 0000000000..2ce2f6448b --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/AnyXmlReader.java @@ -0,0 +1,79 @@ +/* + * 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.netconf.cli.reader.impl; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput; +import static org.opendaylight.controller.netconf.cli.io.IOUtil.listType; + +import com.google.common.base.Optional; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.NodeFactory; +import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +public class AnyXmlReader extends AbstractReader { + + public AnyXmlReader(final ConsoleIO console, final SchemaContext schemaContext) { + super(console, schemaContext); + } + + public AnyXmlReader(final ConsoleIO console, final SchemaContext schemaContext, final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + } + + @Override + protected List> readWithContext(final AnyXmlSchemaNode schemaNode) throws IOException, ReadingException { + console.writeLn(listType(schemaNode) + " " + schemaNode.getQName().getLocalName()); + + final String rawValue = console.read(); + + Node newNode = null; + if (!isSkipInput(rawValue)) { + final Optional> value = tryParse(rawValue); + + if (value.isPresent()) { + newNode = NodeFactory.createImmutableCompositeNode(schemaNode.getQName(), null, + Collections.> singletonList(value.get())); + } else { + newNode = NodeFactory.createImmutableSimpleNode(schemaNode.getQName(), null, rawValue); + } + } + + final List> newNodes = new ArrayList<>(); + newNodes.add(newNode); + return newNodes; + } + + private Optional> tryParse(final String rawValue) { + try { + final Document dom = XmlUtil.readXmlToDocument(rawValue); + return Optional.> of(XmlDocumentUtils.toDomNode(dom)); + } catch (SAXException | IOException e) { + // TODO log + return Optional.absent(); + } + } + + @Override + protected ConsoleContext getContext(final AnyXmlSchemaNode schemaNode) { + return new BaseConsoleContext<>(schemaNode); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/BasicDataHolderReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/BasicDataHolderReader.java new file mode 100644 index 0000000000..ef41e7fe7a --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/BasicDataHolderReader.java @@ -0,0 +1,255 @@ +/* + * 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.netconf.cli.reader.impl; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput; +import static org.opendaylight.controller.netconf.cli.io.IOUtil.listType; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.BiMap; +import com.google.common.collect.Collections2; +import com.google.common.collect.HashBiMap; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.io.IOUtil; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.NodeFactory; +import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class BasicDataHolderReader extends AbstractReader { + + private static final Logger LOG = LoggerFactory.getLogger(BasicDataHolderReader.class); + private DataHolderCompleter currentCompleter; + + public BasicDataHolderReader(final ConsoleIO console, final SchemaContext schemaContext, + final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + } + + public BasicDataHolderReader(final ConsoleIO console, final SchemaContext schemaContext) { + super(console, schemaContext); + } + + @Override + public List> readWithContext(final T schemaNode) throws IOException, ReadingException { + TypeDefinition type = getType(schemaNode); + console.formatLn("Submit %s %s(%s)", listType(schemaNode), schemaNode.getQName().getLocalName(), type.getQName().getLocalName()); + + while (baseTypeFor(type) instanceof UnionTypeDefinition) { + final Optional> optionalTypeDef = new UnionTypeReader(console).read(type); + if (!optionalTypeDef.isPresent()) { + return postSkipOperations(schemaNode); + } + type = optionalTypeDef.get(); + } + + if (currentCompleter == null) { + currentCompleter = getBaseCompleter(schemaNode); + } + + // TODO what if type is leafref, instance-identifier? + + // Handle empty type leaf by question + if (isEmptyType(type)) { + final Optional shouldAddEmpty = new DecisionReader().read(console, "Add empty type leaf %s ?", + schemaNode.getQName().getLocalName()); + if (shouldAddEmpty.isPresent()) { + if (shouldAddEmpty.get()) { + return wrapValue(schemaNode, ""); + } else { + return Collections.emptyList(); + } + } else { + return postSkipOperations(schemaNode); + } + } + + final String rawValue = readValue(); + if (isSkipInput(rawValue)) { + return postSkipOperations(schemaNode); + } + + final Object resolvedValue = currentCompleter.resolveValue(rawValue); + + // Reset state TODO should be in finally + currentCompleter = null; + return wrapValue(schemaNode, resolvedValue); + } + + private List> postSkipOperations(final DataSchemaNode schemaNode) throws IOException { + console.formatLn("Skipping %s", schemaNode.getQName()); + return Collections.emptyList(); + } + + private TypeDefinition baseTypeFor(final TypeDefinition type) { + if (type.getBaseType() != null) { + return baseTypeFor(type.getBaseType()); + } + return type; + } + + protected String readValue() throws IOException { + return console.read(); + } + + private List> wrapValue(final T schemaNode, final Object value) { + final Node newNode = NodeFactory.createImmutableSimpleNode(schemaNode.getQName(), null, value); + return Collections.> singletonList(newNode); + } + + protected abstract TypeDefinition getType(final T schemaNode); + + protected final DataHolderCompleter getBaseCompleter(final T schemaNode) { + final TypeDefinition type = getType(schemaNode); + final DataHolderCompleter currentCompleter; + + // Add enum completer + if (type instanceof EnumTypeDefinition) { + currentCompleter = new EnumDataHolderCompleter(type); + } else if (type instanceof IdentityrefTypeDefinition) { + currentCompleter = new IdentityRefDataHolderCompleter(type, getSchemaContext()); + } else { + currentCompleter = new GeneralDataHolderCompleter(type); + } + this.currentCompleter = currentCompleter; + return currentCompleter; + } + + private static interface DataHolderCompleter extends Completer { + + Object resolveValue(String rawValue) throws ReadingException; + } + + private static class GeneralDataHolderCompleter implements DataHolderCompleter { + + private final Optional>> codec; + private final TypeDefinition type; + + public GeneralDataHolderCompleter(final TypeDefinition type) { + this.type = type; + codec = getCodecForType(type); + } + + protected TypeDefinition getType() { + return type; + } + + private Optional>> getCodecForType( + final TypeDefinition type) { + if (type != null) { + return Optional + .>> fromNullable(TypeDefinitionAwareCodec + .from(type)); + } + return Optional.absent(); + } + + @Override + public Object resolveValue(final String rawValue) throws ReadingException { + try { + return codec.isPresent() ? codec.get().deserialize(rawValue) : rawValue; + } catch (final RuntimeException e) { + final String message = "It wasn't possible deserialize value " + rawValue + "."; + LOG.error(message, e); + throw new ReadingException(message, e); + } + } + + @Override + public int complete(final String buffer, final int cursor, final List candidates) { + return 0; + } + } + + private static final class EnumDataHolderCompleter extends GeneralDataHolderCompleter { + + public EnumDataHolderCompleter(final TypeDefinition type) { + super(type); + } + + @Override + public Object resolveValue(final String rawValue) throws ReadingException { + return super.resolveValue(rawValue); + } + + @Override + public int complete(final String buffer, final int cursor, final List candidates) { + return new StringsCompleter(Collections2.transform(((EnumTypeDefinition) getType()).getValues(), + new Function() { + @Override + public String apply(final EnumPair input) { + return input.getName(); + } + })).complete(buffer, cursor, candidates); + } + } + + private static final class IdentityRefDataHolderCompleter extends GeneralDataHolderCompleter { + + private final BiMap identityMap; + + public IdentityRefDataHolderCompleter(final TypeDefinition type, final SchemaContext schemaContext) { + super(type); + this.identityMap = getIdentityMap(schemaContext); + } + + private static BiMap getIdentityMap(final SchemaContext schemaContext) { + final BiMap identityMap = HashBiMap.create(); + for (final Module module : schemaContext.getModules()) { + for (final IdentitySchemaNode identity : module.getIdentities()) { + identityMap.put(getIdentityName(identity, module), identity.getQName()); + } + } + return identityMap; + } + + private static String getIdentityName(final IdentitySchemaNode rpcDefinition, final Module module) { + return IOUtil.qNameToKeyString(rpcDefinition.getQName(), module.getName()); + } + + @Override + public Object resolveValue(final String rawValue) throws ReadingException { + final QName qName = identityMap.get(rawValue); + if (qName == null) { + throw new ReadingException("No identity found for " + rawValue + " available " + identityMap.keySet()); + } + return qName; + } + + @Override + public int complete(final String buffer, final int cursor, final List candidates) { + + return new StringsCompleter(Collections2.transform(((IdentityrefTypeDefinition) getType()).getIdentity() + .getDerivedIdentities(), new Function() { + @Override + public String apply(final IdentitySchemaNode input) { + return identityMap.inverse().get(input.getQName()); + } + })).complete(buffer, cursor, candidates); + } + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ChoiceReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ChoiceReader.java new file mode 100644 index 0000000000..1e69fbb774 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ChoiceReader.java @@ -0,0 +1,139 @@ +/* + * 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.netconf.cli.reader.impl; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput; + +import com.google.common.base.Function; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; +import org.opendaylight.controller.netconf.cli.CommandArgHandlerRegistry; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.NodeFactory; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ChoiceReader extends AbstractReader { + + private static final Logger LOG = LoggerFactory.getLogger(ChoiceReader.class); + + private final CommandArgHandlerRegistry argumentHandlerRegistry; + + public ChoiceReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, + final SchemaContext schemaContext) { + super(console, schemaContext); + this.argumentHandlerRegistry = argumentHandlerRegistry; + } + + public ChoiceReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, + final SchemaContext schemaContext, final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + this.argumentHandlerRegistry = argumentHandlerRegistry; + } + + @Override + public List> readWithContext(final ChoiceNode choiceNode) throws IOException, ReadingException { + final Map availableCases = collectAllCases(choiceNode); + console.formatLn("Select case for choice %s from: %s", choiceNode.getQName().getLocalName(), + formatSet(availableCases.keySet())); + + ChoiceCaseNode selectedCase = null; + final String rawValue = console.read(); + if (isSkipInput(rawValue)) { + return Collections.emptyList(); + } + + selectedCase = availableCases.get(rawValue); + if (selectedCase == null) { + final String message = String.format("Incorrect value (%s) for choice %s was selected.", rawValue, + choiceNode.getQName().getLocalName()); + LOG.error(message); + throw new ReadingException(message); + } + + return readSelectedCase(selectedCase); + } + + protected List> readSelectedCase(final ChoiceCaseNode selectedCase) throws ReadingException { + // IF there is a case that contains only one Empty type leaf, create the + // leaf without question, since the case was selected + if (containsOnlyOneEmptyLeaf(selectedCase)) { + final Node newNode = NodeFactory.createImmutableSimpleNode(selectedCase.getChildNodes().iterator() + .next().getQName(), null, null); + return Collections.> singletonList(newNode); + } + + final List> newNodes = new ArrayList<>(); + for (final DataSchemaNode schemaNode : selectedCase.getChildNodes()) { + newNodes.addAll(argumentHandlerRegistry.getGenericReader(getSchemaContext(), getReadConfigNode()).read( + schemaNode)); + } + return newNodes; + } + + private Object formatSet(final Set values) { + final StringBuilder formatedValues = new StringBuilder(); + for (final String value : values) { + formatedValues.append("\n "); + formatedValues.append(value); + } + return formatedValues.toString(); + } + + private boolean containsOnlyOneEmptyLeaf(final ChoiceCaseNode selectedCase) { + if (selectedCase.getChildNodes().size() != 1) { + return false; + } + final DataSchemaNode next = selectedCase.getChildNodes().iterator().next(); + if (next instanceof LeafSchemaNode) { + final TypeDefinition type = ((LeafSchemaNode) next).getType(); + if (isEmptyType(type)) { + return true; + } + } + return false; + } + + private Map collectAllCases(final ChoiceNode schemaNode) { + return Maps.uniqueIndex(schemaNode.getCases(), new Function() { + @Override + public String apply(final ChoiceCaseNode input) { + return input.getQName().getLocalName(); + } + }); + } + + @Override + protected ConsoleContext getContext(final ChoiceNode schemaNode) { + return new BaseConsoleContext(schemaNode) { + @Override + public List getAdditionalCompleters() { + return Collections + . singletonList(new StringsCompleter(collectAllCases(schemaNode).keySet())); + } + }; + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ContainerReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ContainerReader.java new file mode 100644 index 0000000000..8e9a29ef5a --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ContainerReader.java @@ -0,0 +1,97 @@ +/* + * 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.netconf.cli.reader.impl; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import org.opendaylight.controller.netconf.cli.CommandArgHandlerRegistry; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class ContainerReader extends AbstractReader { + + private final CommandArgHandlerRegistry argumentHandlerRegistry; + private static final InputArgsLocalNameComparator CONTAINER_CHILDS_SORTER = new InputArgsLocalNameComparator(); + + public ContainerReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, + final SchemaContext schemaContext) { + super(console, schemaContext); + this.argumentHandlerRegistry = argumentHandlerRegistry; + } + + public ContainerReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, + final SchemaContext schemaContext, final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + this.argumentHandlerRegistry = argumentHandlerRegistry; + } + + @Override + public List> readWithContext(final ContainerSchemaNode containerNode) throws IOException, ReadingException { + console.formatLn("Submit child nodes for container: %s, %s", containerNode.getQName().getLocalName(), + Collections2.transform(containerNode.getChildNodes(), new Function() { + @Override + public String apply(final DataSchemaNode input) { + return input.getQName().getLocalName(); + } + })); + + final CompositeNodeBuilder compositeNodeBuilder = ImmutableCompositeNode.builder(); + compositeNodeBuilder.setQName(containerNode.getQName()); + final SeparatedNodes separatedNodes = SeparatedNodes.separateNodes(containerNode, getReadConfigNode()); + for (final DataSchemaNode childNode : sortChildren(separatedNodes.getMandatoryNotKey())) { + final List> redNodes = argumentHandlerRegistry.getGenericReader(getSchemaContext(), + getReadConfigNode()).read(childNode); + if (redNodes.isEmpty()) { + console.formatLn("No data specified for mandatory element %s.", childNode.getQName().getLocalName()); + return Collections.emptyList(); + } else { + compositeNodeBuilder.addAll(redNodes); + } + } + + for (final DataSchemaNode childNode : sortChildren(separatedNodes.getOthers())) { + compositeNodeBuilder.addAll(argumentHandlerRegistry.getGenericReader(getSchemaContext(), + getReadConfigNode()).read(childNode)); + } + return Collections.> singletonList(compositeNodeBuilder.toInstance()); + } + + private List sortChildren(final Set unsortedNodes) { + final List childNodes = Lists.newArrayList(unsortedNodes); + Collections.sort(childNodes, CONTAINER_CHILDS_SORTER); + return childNodes; + } + + @Override + protected ConsoleContext getContext(final ContainerSchemaNode schemaNode) { + return new BaseConsoleContext<>(schemaNode); + } + + private static class InputArgsLocalNameComparator implements Comparator { + @Override + public int compare(final DataSchemaNode o1, final DataSchemaNode o2) { + return o1.getQName().getLocalName().compareTo(o2.getQName().getLocalName()); + } + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/DecisionReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/DecisionReader.java new file mode 100644 index 0000000000..b1e9a43298 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/DecisionReader.java @@ -0,0 +1,68 @@ +/* + * 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.netconf.cli.reader.impl; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.SKIP; + +import com.google.common.base.Optional; +import java.io.IOException; +import jline.console.completer.AggregateCompleter; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; +import jline.internal.Log; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.io.IOUtil; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; + +public class DecisionReader { + + private static final String YES = "Y"; + private static final String NO = "N"; + public static final Completer YES_NO_COMPLETER = new StringsCompleter(YES, NO); + + public Optional read(final ConsoleIO console, final String questionMessageBlueprint, + final Object... questionMessageArgs) throws IOException, ReadingException { + final ConsoleContext ctx = getContext(); + console.enterContext(ctx); + try { + console.formatLn(questionMessageBlueprint, questionMessageArgs); + final String rawValue = console.read(); + if (YES.equals(rawValue.toUpperCase())) { + return Optional.of(Boolean.TRUE); + } else if (NO.equals(rawValue.toUpperCase())) { + return Optional.of(Boolean.FALSE); + } else if (SKIP.equals(rawValue)) { + return Optional.absent(); + } else { + final String message = String.format("Incorrect possibility (%s) was selected", rawValue); + Log.error(message); + throw new ReadingException(message); + } + } finally { + console.leaveContext(); + } + } + + private static ConsoleContext getContext() { + return new ConsoleContext() { + + @Override + public Optional getPrompt() { + return Optional.absent(); + } + + @Override + public Completer getCompleter() { + return new AggregateCompleter(YES_NO_COMPLETER, new StringsCompleter(IOUtil.SKIP)); + } + + }; + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/GenericListReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/GenericListReader.java new file mode 100644 index 0000000000..6cf8eb2344 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/GenericListReader.java @@ -0,0 +1,69 @@ +/* + * 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.netconf.cli.reader.impl; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.listType; + +import com.google.common.base.Optional; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.GenericListEntryReader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GenericListReader extends AbstractReader { + private static final Logger LOG = LoggerFactory.getLogger(GenericListReader.class); + + private final GenericListEntryReader concreteListEntryReader; + + public GenericListReader(final ConsoleIO console, final GenericListEntryReader concreteListEntryReader, + final SchemaContext schemaContext) { + super(console, schemaContext); + this.concreteListEntryReader = concreteListEntryReader; + } + + public GenericListReader(final ConsoleIO console, final GenericListEntryReader concreteListEntryReader, + final SchemaContext schemaContext, final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + this.concreteListEntryReader = concreteListEntryReader; + } + + @Override + public List> readWithContext(final T schemaNode) throws IOException, ReadingException { + final List> newNodes = new ArrayList<>(); + Optional readNextListEntry = Optional.of(Boolean.TRUE); + console.formatLn("Reading collection type argument: %s", schemaNode.getQName().getLocalName()); + while (readNextListEntry.isPresent() && readNextListEntry.get()) { + try { + newNodes.addAll(concreteListEntryReader.read(schemaNode)); + } catch (final ReadingException e) { + console.writeLn(e.getMessage()); + } + readNextListEntry = new DecisionReader().read(console, "Add other entry to " + listType(schemaNode) + " " + + schemaNode.getQName().getLocalName() + " " + " [Y|N]?"); + } + console.formatLn("Collection type argument: %s read finished", schemaNode.getQName().getLocalName()); + + return newNodes; + } + + @Override + protected ConsoleContext getContext(final T schemaNode) { + return new BaseConsoleContext<>(schemaNode); + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/GenericReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/GenericReader.java new file mode 100644 index 0000000000..8fbfbb7e3a --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/GenericReader.java @@ -0,0 +1,134 @@ +/* + * 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.netconf.cli.reader.impl; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.opendaylight.controller.netconf.cli.CommandArgHandlerRegistry; +import org.opendaylight.controller.netconf.cli.commands.CommandConstants; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.GenericListEntryReader; +import org.opendaylight.controller.netconf.cli.reader.Reader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode; + +public class GenericReader extends AbstractReader { + + private final CommandArgHandlerRegistry argumentHandlerRegistry; + + public GenericReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, + final SchemaContext schemaContext) { + super(console, schemaContext); + this.argumentHandlerRegistry = argumentHandlerRegistry; + } + + public GenericReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, + final SchemaContext schemaContext, final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + this.argumentHandlerRegistry = argumentHandlerRegistry; + } + + @Override + protected List> readWithContext(final DataSchemaNode schemaNode) throws IOException, ReadingException { + final Optional>> customReaderClassOpt = tryGetCustomHandler(schemaNode); + + if (customReaderClassOpt.isPresent()) { + // TODO resolve class cast of generic custom readers + final Reader customReaderInstance = (Reader) argumentHandlerRegistry + .getCustomReader(customReaderClassOpt.get()); + Preconditions.checkNotNull(customReaderInstance, "Unknown custom reader: %s", customReaderClassOpt.get()); + return customReaderInstance.read(schemaNode); + } else { + return readGeneric(schemaNode); + } + + // TODO reuse instances + } + + private List> readGeneric(final DataSchemaNode schemaNode) throws ReadingException, IOException { + final List> newNodes = new ArrayList<>(); + boolean isRedCorrectly = false; + do { + try { + if (schemaNode instanceof LeafSchemaNode) { + return new LeafReader(console, getSchemaContext(), getReadConfigNode()) + .read((LeafSchemaNode) schemaNode); + } else if (schemaNode instanceof ContainerSchemaNode) { + return new ContainerReader(console, argumentHandlerRegistry, getSchemaContext(), + getReadConfigNode()).read((ContainerSchemaNode) schemaNode); + } else if (schemaNode instanceof ListSchemaNode) { + final GenericListEntryReader entryReader = new ListEntryReader(console, + argumentHandlerRegistry, getSchemaContext(), getReadConfigNode()); + return new GenericListReader<>(console, entryReader, getSchemaContext(), getReadConfigNode()) + .read((ListSchemaNode) schemaNode); + } else if (schemaNode instanceof LeafListSchemaNode) { + final GenericListEntryReader entryReader = new LeafListEntryReader(console, + getSchemaContext(), getReadConfigNode()); + return new GenericListReader<>(console, entryReader, getSchemaContext(), getReadConfigNode()) + .read((LeafListSchemaNode) schemaNode); + } else if (schemaNode instanceof ChoiceNode) { + return new ChoiceReader(console, argumentHandlerRegistry, getSchemaContext(), getReadConfigNode()) + .read((ChoiceNode) schemaNode); + } else if (schemaNode instanceof AnyXmlSchemaNode) { + return new AnyXmlReader(console, getSchemaContext(), getReadConfigNode()) + .read((AnyXmlSchemaNode) schemaNode); + } + isRedCorrectly = true; + } catch (final ReadingException e) { + console.writeLn(e.getMessage()); + } + } while (!isRedCorrectly); + return newNodes; + } + + @Override + protected ConsoleContext getContext(final DataSchemaNode schemaNode) { + // return null context, leave context to specific implementations + return NULL_CONTEXT; + } + + private Optional> tryGetCustomHandler(final DataSchemaNode dataSchemaNode) { + + for (final UnknownSchemaNode unknownSchemaNode : dataSchemaNode.getUnknownSchemaNodes()) { + + if (isExtenstionForCustomHandler(unknownSchemaNode)) { + final String argumentHandlerClassName = unknownSchemaNode.getNodeParameter(); + try { + final Class argumentClass = Class.forName(argumentHandlerClassName); + // TODO add check before cast + return Optional.> of((Class) argumentClass); + } catch (final ClassNotFoundException e) { + throw new IllegalArgumentException("Unknown custom reader class " + argumentHandlerClassName + + " for: " + dataSchemaNode.getQName()); + } + } + } + + return Optional.absent(); + } + + private boolean isExtenstionForCustomHandler(final UnknownSchemaNode unknownSchemaNode) { + final QName qName = unknownSchemaNode.getExtensionDefinition().getQName(); + return qName.equals(CommandConstants.ARG_HANDLER_EXT_QNAME); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/LeafListEntryReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/LeafListEntryReader.java new file mode 100644 index 0000000000..4ba3478365 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/LeafListEntryReader.java @@ -0,0 +1,53 @@ +/* + * 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.netconf.cli.reader.impl; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import java.util.List; +import jline.console.completer.Completer; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.GenericListEntryReader; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; + +class LeafListEntryReader extends BasicDataHolderReader implements + GenericListEntryReader { + + public LeafListEntryReader(final ConsoleIO console, final SchemaContext schemaContext) { + super(console, schemaContext); + } + + public LeafListEntryReader(final ConsoleIO console, final SchemaContext schemaContext, final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + } + + @Override + protected TypeDefinition getType(final LeafListSchemaNode schemaNode) { + return schemaNode.getType(); + } + + @Override + protected ConsoleContext getContext(final LeafListSchemaNode schemaNode) { + return new BaseConsoleContext(schemaNode) { + + @Override + public Optional getPrompt() { + return Optional.of("[entry]"); + } + + @Override + protected List getAdditionalCompleters() { + return Lists. newArrayList(getBaseCompleter(getDataSchemaNode())); + } + }; + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/LeafReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/LeafReader.java new file mode 100644 index 0000000000..9a847f690a --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/LeafReader.java @@ -0,0 +1,51 @@ +/* + * 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.netconf.cli.reader.impl; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import java.util.List; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; + +public class LeafReader extends BasicDataHolderReader { + + public LeafReader(final ConsoleIO console, final SchemaContext schemaContext) { + super(console, schemaContext); + } + + public LeafReader(final ConsoleIO console, final SchemaContext schemaContext, final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + } + + @Override + protected TypeDefinition getType(final LeafSchemaNode schemaNode) { + return schemaNode.getType(); + } + + @Override + protected ConsoleContext getContext(final LeafSchemaNode schemaNode) { + return new BaseConsoleContext(schemaNode) { + @Override + public List getAdditionalCompleters() { + final List completers = Lists. newArrayList(getBaseCompleter(schemaNode)); + final Optional defaultValue = getDefaultValue(schemaNode); + if (defaultValue.isPresent()) { + completers.add(new StringsCompleter(defaultValue.get())); + } + return completers; + } + }; + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ListEntryReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ListEntryReader.java new file mode 100644 index 0000000000..97f76944d9 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/ListEntryReader.java @@ -0,0 +1,143 @@ +/* + * 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.netconf.cli.reader.impl; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.Collections2; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.opendaylight.controller.netconf.cli.CommandArgHandlerRegistry; +import org.opendaylight.controller.netconf.cli.io.BaseConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.reader.AbstractReader; +import org.opendaylight.controller.netconf.cli.reader.GenericListEntryReader; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ListEntryReader extends AbstractReader implements GenericListEntryReader { + private static final Logger LOG = LoggerFactory.getLogger(ListEntryReader.class); + + private final CommandArgHandlerRegistry argumentHandlerRegistry; + + public ListEntryReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, + final SchemaContext schemaContext) { + super(console, schemaContext); + this.argumentHandlerRegistry = argumentHandlerRegistry; + } + + public ListEntryReader(final ConsoleIO console, final CommandArgHandlerRegistry argumentHandlerRegistry, + final SchemaContext schemaContext, final boolean readConfigNode) { + super(console, schemaContext, readConfigNode); + this.argumentHandlerRegistry = argumentHandlerRegistry; + } + + @Override + public List> readWithContext(final ListSchemaNode listNode) throws IOException, ReadingException { + console.formatLn("Submit child nodes for list entry: %s, %s", listNode.getQName().getLocalName(), + Collections2.transform(listNode.getChildNodes(), new Function() { + @Override + public String apply(final DataSchemaNode input) { + return input.getQName().getLocalName(); + } + })); + + final String listName = listNode.getQName().getLocalName(); + final CompositeNodeBuilder compositeNodeBuilder = ImmutableCompositeNode.builder(); + compositeNodeBuilder.setQName(listNode.getQName()); + + final SeparatedNodes separatedChildNodes = SeparatedNodes.separateNodes(listNode, getReadConfigNode()); + + final List> nodes = readKeys(separatedChildNodes.getKeyNodes()); + nodes.addAll(readMandatoryNotKeys(separatedChildNodes.getMandatoryNotKey())); + if (!separatedChildNodes.getOthers().isEmpty()) { + final Optional readNodesWhichAreNotKey = new DecisionReader().read(console, + "Add non-key, non-mandatory nodes to list %s? [Y|N]", listName); + if (readNodesWhichAreNotKey.isPresent() && readNodesWhichAreNotKey.get()) { + nodes.addAll(readNotKeys(separatedChildNodes.getOthers())); + } + } + + if (!nodes.isEmpty()) { + compositeNodeBuilder.addAll(nodes); + return Collections.> singletonList(compositeNodeBuilder.toInstance()); + } else { + return Collections.emptyList(); + } + } + + private List> readKeys(final Set keys) throws ReadingException, IOException { + final List> newNodes = new ArrayList<>(); + console.writeLn("Reading keys:"); + for (final DataSchemaNode key : keys) { + final List> readKey = new LeafReader(console, getSchemaContext(), getReadConfigNode()) + .read((LeafSchemaNode) key); + if (readKey.size() != 1) { + final String message = String.format( + "Value for key element %s has to be set. Creation of this entry is canceled.", key.getQName() + .getLocalName()); + LOG.error(message); + throw new ReadingException(message); + } + newNodes.addAll(readKey); + } + return newNodes; + } + + private List> readMandatoryNotKeys(final Set mandatoryNotKeys) throws ReadingException, + IOException { + final List> newNodes = new ArrayList<>(); + console.writeLn("Reading mandatory not keys nodes:"); + + for (final DataSchemaNode mandatoryNode : mandatoryNotKeys) { + final List> redValue = argumentHandlerRegistry.getGenericReader(getSchemaContext(), + getReadConfigNode()).read(mandatoryNode); + if (redValue.isEmpty()) { + final String message = String.format( + "Value for mandatory element %s has to be set. Creation of this entry is canceled.", + mandatoryNode.getQName().getLocalName()); + LOG.error(message); + throw new ReadingException(message); + } + newNodes.addAll(redValue); + } + return newNodes; + } + + private List> readNotKeys(final Set notKeys) throws ReadingException { + final List> newNodes = new ArrayList<>(); + for (final DataSchemaNode notKey : notKeys) { + newNodes.addAll(argumentHandlerRegistry.getGenericReader(getSchemaContext(), getReadConfigNode()).read( + notKey)); + } + return newNodes; + } + + @Override + protected ConsoleContext getContext(final ListSchemaNode schemaNode) { + return new BaseConsoleContext(schemaNode) { + @Override + public Optional getPrompt() { + return Optional.of("[entry]"); + } + }; + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/SeparatedNodes.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/SeparatedNodes.java new file mode 100644 index 0000000000..e05f7865a8 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/SeparatedNodes.java @@ -0,0 +1,77 @@ +/* + * 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.netconf.cli.reader.impl; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +class SeparatedNodes { + private final Set keyNodes; + private final Set mandatoryNotKey; + private final Set otherNodes; + + public SeparatedNodes(final Set keyNodes, final Set mandatoryNotKey, + final Set otherNodes) { + this.keyNodes = keyNodes; + this.mandatoryNotKey = mandatoryNotKey; + this.otherNodes = otherNodes; + } + + public Set getKeyNodes() { + return keyNodes; + } + + public Set getMandatoryNotKey() { + return mandatoryNotKey; + } + + public Set getOthers() { + return otherNodes; + } + + static SeparatedNodes separateNodes(final DataNodeContainer dataNodeContainer) { + return separateNodes(dataNodeContainer, false); + } + + static SeparatedNodes separateNodes(final DataNodeContainer dataNodeContainer, final boolean removeConfigFalseNodes) { + final Set keys = new HashSet<>(); + final Set mandatoryNotKeys = new HashSet<>(); + final Set others = new HashSet<>(); + + List keyQNames = Collections.emptyList(); + if (dataNodeContainer instanceof ListSchemaNode) { + keyQNames = ((ListSchemaNode) dataNodeContainer).getKeyDefinition(); + } + + for (final DataSchemaNode dataSchemaNode : dataNodeContainer.getChildNodes()) { + if (removeConfigFalseNodes) { + if (!dataSchemaNode.isConfiguration()) { + continue; + } + } + if (keyQNames.contains(dataSchemaNode.getQName())) { + Preconditions.checkArgument(dataSchemaNode instanceof LeafSchemaNode); + keys.add(dataSchemaNode); + } else if (dataSchemaNode.getConstraints().isMandatory()) { + mandatoryNotKeys.add(dataSchemaNode); + } else { + others.add(dataSchemaNode); + } + } + + return new SeparatedNodes(keys, mandatoryNotKeys, others); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/UnionTypeReader.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/UnionTypeReader.java new file mode 100644 index 0000000000..e2d186b882 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/reader/impl/UnionTypeReader.java @@ -0,0 +1,126 @@ +/* + * 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.netconf.cli.reader.impl; + +import static org.opendaylight.controller.netconf.cli.io.IOUtil.isSkipInput; + +import com.google.common.base.Optional; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import jline.console.completer.AggregateCompleter; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; +import org.opendaylight.controller.netconf.cli.io.ConsoleContext; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.io.IOUtil; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class UnionTypeReader { + private static final Logger LOG = LoggerFactory.getLogger(UnionTypeReader.class); + + private final ConsoleIO console; + + public UnionTypeReader(final ConsoleIO console) { + this.console = console; + } + + public Optional> read(final TypeDefinition unionTypeDefinition) throws IOException, + ReadingException { + final ConsoleContext context = getContext(unionTypeDefinition); + console.enterContext(context); + try { + final Map> mapping = ((UnionConsoleContext) context).getMenuItemMapping(); + console.formatLn("The element is of type union. Choose concrete type from: %s", mapping.keySet()); + + final String rawValue = console.read(); + if (isSkipInput(rawValue)) { + return Optional.absent(); + } + final TypeDefinition value = mapping.get(rawValue); + if (value != null) { + return Optional.> of(value); + } else { + final String message = String.format("Incorrect type (%s) was specified for union type definition", rawValue); + LOG.error(message); + throw new ReadingException(message); + } + } finally { + console.leaveContext(); + } + } + + private UnionConsoleContext getContext(final TypeDefinition typeDefinition) { + return new UnionConsoleContext(typeDefinition); + } + + private class UnionConsoleContext implements ConsoleContext { + + private final TypeDefinition typeDef; + private final Map> menuItemsToTypeDefinitions = new HashMap<>(); + + public UnionConsoleContext(final TypeDefinition typeDef) { + this.typeDef = typeDef; + } + + @Override + public Optional getPrompt() { + return Optional.of("type[" + typeDef.getQName().getLocalName() + "]"); + } + + @Override + public Completer getCompleter() { + List> subtypesForMenu = resolveSubtypesFrom(typeDef); + if (subtypesForMenu.isEmpty()) { + subtypesForMenu = Collections.> singletonList(typeDef); + } + final Collection menuItems = toMenuItem(subtypesForMenu); + return new AggregateCompleter(new StringsCompleter(menuItems), new StringsCompleter(IOUtil.SKIP)); + } + + public Map> getMenuItemMapping() { + return menuItemsToTypeDefinitions; + } + + private Collection toMenuItem(final List> allTypesBehindUnion) { + final List result = new ArrayList(); + for (final TypeDefinition type : allTypesBehindUnion) { + final String menuItem = type.getQName().getLocalName(); + menuItemsToTypeDefinitions.put(menuItem, type); + result.add(menuItem); + } + return result; + } + + /** + * + * If union type is found in potentialEndTypeCandidate as subtype then + * it these subtypes become candidates. + * + * @param potentialEndTypeCandidate + * candidate to node which has no union subtype + */ + private List> resolveSubtypesFrom(final TypeDefinition potentialEndTypeCandidate) { + if (potentialEndTypeCandidate instanceof UnionTypeDefinition) { + return ((UnionTypeDefinition) potentialEndTypeCandidate).getTypes(); + } + if (potentialEndTypeCandidate.getBaseType() == null) { + return Collections.emptyList(); + } + return resolveSubtypesFrom(potentialEndTypeCandidate.getBaseType()); + } + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/OutFormatter.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/OutFormatter.java new file mode 100644 index 0000000000..bed27b43be --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/OutFormatter.java @@ -0,0 +1,66 @@ +/* + * 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.netconf.cli.writer; + +public class OutFormatter { + + public static final String INDENT_STEP = " "; + public static final String COMPOSITE_OPEN_NODE = " {"; + public static final String COMPOSITE_CLOSE_NODE = "}"; + public static final String NEW_LINE = "\n"; + + int indentLevel = -1; + private String currentIndent = ""; + + public OutFormatter indent(final StringBuilder buffer) { + buffer.append(currentIndent); + return this; + } + + public OutFormatter openComposite(final StringBuilder buffer) { + buffer.append(COMPOSITE_OPEN_NODE); + return this; + } + + public OutFormatter closeCompositeWithIndent(final StringBuilder buffer) { + buffer.append(currentIndent); + buffer.append(COMPOSITE_CLOSE_NODE); + return this; + } + + public OutFormatter newLine(final StringBuilder buffer) { + buffer.append(NEW_LINE); + return this; + } + + private void prepareIndent() { + final StringBuilder output = new StringBuilder(); + for (int i = 0; i < indentLevel; i++) { + output.append(INDENT_STEP); + } + currentIndent = output.toString(); + } + + public OutFormatter increaseIndent() { + indentLevel++; + prepareIndent(); + return this; + } + + public OutFormatter decreaseIndent() { + indentLevel--; + prepareIndent(); + return this; + } + + public OutFormatter addStringWithIndent(final StringBuilder buffer, final String value) { + indent(buffer); + buffer.append(value); + return this; + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/WriteException.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/WriteException.java new file mode 100644 index 0000000000..b2cc456502 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/WriteException.java @@ -0,0 +1,29 @@ +/* + * 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.netconf.cli.writer; + +public class WriteException extends Exception { + + private static final long serialVersionUID = 8401242676753560336L; + + public WriteException(final String msg, final Exception e) { + super(msg, e); + } + + public WriteException(final String msg) { + super(msg); + } + + public static class IncorrectNumberOfNodes extends WriteException { + private static final long serialVersionUID = 8910285140705622920L; + + public IncorrectNumberOfNodes(final String msg) { + super(msg); + } + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/Writer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/Writer.java new file mode 100644 index 0000000000..ba3d876d84 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/Writer.java @@ -0,0 +1,21 @@ +/* + * 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.netconf.cli.writer; + +import java.util.List; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +/** + * Generic handler(writer) of output elements for commands + */ +public interface Writer { + + void write(T dataSchemaNode, List> dataNodes) throws WriteException; + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/custom/DataWriter.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/custom/DataWriter.java new file mode 100644 index 0000000000..3724ecbb15 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/custom/DataWriter.java @@ -0,0 +1,57 @@ +/* + * 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.netconf.cli.writer.custom; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.controller.netconf.cli.writer.WriteException; +import org.opendaylight.controller.netconf.cli.writer.impl.AbstractWriter; +import org.opendaylight.controller.netconf.cli.writer.impl.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlDocumentUtils; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class DataWriter extends AbstractWriter { + + private final OutFormatter out; + private final SchemaContext remoteSchemaContext; + + public DataWriter(final ConsoleIO console, final OutFormatter out, final SchemaContext remoteSchemaContext) { + super(console); + this.out = out; + this.remoteSchemaContext = remoteSchemaContext; + } + + @Override + protected void writeInner(final DataSchemaNode dataSchemaNode, final List> dataNodes) throws IOException, WriteException { + Preconditions.checkArgument(dataNodes.size() == 1, "Expected only 1 element for data node"); + final Node dataNode = dataNodes.get(0); + Preconditions.checkArgument(dataNode instanceof CompositeNode, "Unexpected node type: %s, should be %s", dataNode, CompositeNode.class); + + StringBuilder output = new StringBuilder(); + out.increaseIndent().addStringWithIndent(output, dataSchemaNode.getQName().getLocalName()).openComposite(output); + console.writeLn(output.toString()); + + for (final Node childNode : ((CompositeNode) dataNode).getValue()) { + final Optional schemaNode = XmlDocumentUtils.findFirstSchema(childNode.getNodeType(), remoteSchemaContext.getDataDefinitions()); + Preconditions.checkState(schemaNode.isPresent(), "Unknown data node %s, not defined in schema", childNode.getNodeType()); + new NormalizedNodeWriter(console, out).write(schemaNode.get(), Collections.>singletonList(childNode)); + } + + output = new StringBuilder(); + out.decreaseIndent().closeCompositeWithIndent(output); + console.writeLn(output.toString()); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/AbstractWriter.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/AbstractWriter.java new file mode 100644 index 0000000000..f9c4e8447c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/AbstractWriter.java @@ -0,0 +1,37 @@ +/* + * 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.netconf.cli.writer.impl; + +import java.io.IOException; +import java.util.List; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.writer.WriteException; +import org.opendaylight.controller.netconf.cli.writer.Writer; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +public abstract class AbstractWriter implements Writer { + + protected ConsoleIO console; + + public AbstractWriter(final ConsoleIO console) { + this.console = console; + } + + @Override + public void write(final T dataSchemaNode, final List> dataNodes) throws WriteException { + try { + writeInner(dataSchemaNode, dataNodes); + } catch (final IOException e) { + throw new WriteException("Unable to write data to output for " + dataSchemaNode.getQName(), e); + } + } + + protected abstract void writeInner(final T dataSchemaNode, final List> dataNodes) throws IOException, + WriteException; +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/AugmentationNodeCliSerializer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/AugmentationNodeCliSerializer.java new file mode 100644 index 0000000000..626bc4ee77 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/AugmentationNodeCliSerializer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.AugmentationNodeBaseSerializer; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; +final class AugmentationNodeCliSerializer extends AugmentationNodeBaseSerializer { + + private final NodeSerializerDispatcher dispatcher; + private final OutFormatter out; + + AugmentationNodeCliSerializer(final OutFormatter out, final NodeSerializerDispatcher dispatcher) { + this.out = Preconditions.checkNotNull(out); + this.dispatcher = Preconditions.checkNotNull(dispatcher); + } + + @Override + public Iterable serialize(final AugmentationSchema schema, final AugmentationNode node) { + final StringBuilder output = new StringBuilder(); + out.increaseIndent(); + out.addStringWithIndent(output, "augment"); + out.openComposite(output); + out.newLine(output); + + for (final String childOutput : super.serialize(schema, node)) { + output.append(childOutput); + out.newLine(output); + } + + out.closeCompositeWithIndent(output); + out.decreaseIndent(); + return Collections.singletonList(output.toString()); + } + + @Override + protected NodeSerializerDispatcher getNodeDispatcher() { + return dispatcher; + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/ChoiceNodeCliSerializer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/ChoiceNodeCliSerializer.java new file mode 100644 index 0000000000..62845ad129 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/ChoiceNodeCliSerializer.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.ChoiceNodeBaseSerializer; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; + +final class ChoiceNodeCliSerializer extends ChoiceNodeBaseSerializer { + private final NodeSerializerDispatcher dispatcher; + private final OutFormatter out; + + ChoiceNodeCliSerializer(final OutFormatter out, final NodeSerializerDispatcher dispatcher) { + this.out = Preconditions.checkNotNull(out); + this.dispatcher = Preconditions.checkNotNull(dispatcher); + } + + @Override + public Iterable serialize(final ChoiceNode schema, final org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode node) { + final StringBuilder output = new StringBuilder(); + out.increaseIndent(); + out.addStringWithIndent(output, "choice "); + output.append(schema.getQName().getLocalName()); + output.append(" ("); + output.append(detectCase(schema, node)); + output.append(") "); + out.openComposite(output); + out.newLine(output); + + for (final String childOutput : super.serialize(schema, node)) { + output.append(childOutput); + out.newLine(output); + } + + out.closeCompositeWithIndent(output); + out.decreaseIndent(); + return Collections.singletonList(output.toString()); + } + + private String detectCase(final ChoiceNode schema, final org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode node) { + for (final DataContainerChild caseChild : node.getValue()) { + final QName presentChildQName = caseChild.getNodeType(); + for (final ChoiceCaseNode choiceCaseNode : schema.getCases()) { + if (choiceCaseNode.getDataChildByName(presentChildQName) != null) { + // Pick the first case that contains first child node + return choiceCaseNode.getQName().getLocalName(); + } + } + } + + // Should not happen, nodes should come from one of the cases + throw new IllegalStateException("Choice node " + node + " does not conform to choice schema " + schema); + } + + @Override + protected NodeSerializerDispatcher getNodeDispatcher() { + return dispatcher; + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/CliOutputFromNormalizedNodeSerializerFactory.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/CliOutputFromNormalizedNodeSerializerFactory.java new file mode 100644 index 0000000000..cab07afe6c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/CliOutputFromNormalizedNodeSerializerFactory.java @@ -0,0 +1,103 @@ +/* + * 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.netconf.cli.writer.impl; + +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +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.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.impl.codec.xml.XmlCodecProvider; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializer; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializerFactory; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +public final class CliOutputFromNormalizedNodeSerializerFactory implements FromNormalizedNodeSerializerFactory { + private final ContainerNodeCliSerializer containerSerializer; + private final ChoiceNodeCliSerializer choiceSerializer; + private final AugmentationNodeCliSerializer augmentSerializer; + private final LeafNodeCliSerializer leafNodeSerializer; + private final LeafSetNodeCliSerializer leafSetSerializer; + private final MapNodeCliSerializer mapNodeSerializer; + private final LeafSetEntryNodeCliSerializer leafSetEntryNodeSerializer; + private final MapEntryNodeCliSerializer mapEntryNodeSerializer; + final NodeSerializerDispatcher dispatcher = new NodeCliSerializerDispatcher(this); + + private CliOutputFromNormalizedNodeSerializerFactory(final OutFormatter out, final XmlCodecProvider codecProvider) { + + containerSerializer = new ContainerNodeCliSerializer(out, dispatcher); + choiceSerializer = new ChoiceNodeCliSerializer(out, dispatcher); + augmentSerializer = new AugmentationNodeCliSerializer(out, dispatcher); + leafNodeSerializer = new LeafNodeCliSerializer(out); + + leafSetEntryNodeSerializer = new LeafSetEntryNodeCliSerializer(out); + leafSetSerializer = new LeafSetNodeCliSerializer(out, leafSetEntryNodeSerializer); + + mapEntryNodeSerializer = new MapEntryNodeCliSerializer(out, dispatcher); + mapNodeSerializer = new MapNodeCliSerializer(out, mapEntryNodeSerializer); + } + + public NodeSerializerDispatcher getDispatcher() { + return dispatcher; + } + + public static CliOutputFromNormalizedNodeSerializerFactory getInstance(final OutFormatter out, + final XmlCodecProvider codecProvider) { + return new CliOutputFromNormalizedNodeSerializerFactory(out, codecProvider); + } + + @Override + public FromNormalizedNodeSerializer getAugmentationNodeSerializer() { + return augmentSerializer; + } + + @Override + public FromNormalizedNodeSerializer getChoiceNodeSerializer() { + return choiceSerializer; + } + + @Override + public FromNormalizedNodeSerializer getContainerNodeSerializer() { + return containerSerializer; + } + + @Override + public FromNormalizedNodeSerializer, LeafSchemaNode> getLeafNodeSerializer() { + return leafNodeSerializer; + } + + @Override + public FromNormalizedNodeSerializer, LeafListSchemaNode> getLeafSetEntryNodeSerializer() { + return leafSetEntryNodeSerializer; + } + + @Override + public FromNormalizedNodeSerializer, LeafListSchemaNode> getLeafSetNodeSerializer() { + return leafSetSerializer; + } + + @Override + public FromNormalizedNodeSerializer getMapEntryNodeSerializer() { + return mapEntryNodeSerializer; + } + + @Override + public FromNormalizedNodeSerializer getMapNodeSerializer() { + return mapNodeSerializer; + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/CompositeNodeWriter.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/CompositeNodeWriter.java new file mode 100644 index 0000000000..57d8f57e97 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/CompositeNodeWriter.java @@ -0,0 +1,55 @@ +/* + * 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.netconf.cli.writer.impl; + +import java.io.IOException; +import java.util.List; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.controller.netconf.cli.writer.WriteException; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.SimpleNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +public class CompositeNodeWriter extends AbstractWriter { + + private final OutFormatter outFormatter; + + public CompositeNodeWriter(final ConsoleIO console, final OutFormatter outFormatter) { + super(console); + this.outFormatter = outFormatter; + } + + @Override + protected void writeInner(final DataSchemaNode dataSchemaNode, final List> dataNodes) throws IOException, WriteException { + final StringBuilder output = new StringBuilder(); + writeNode(dataNodes, output); + console.writeLn(output); + } + + private void writeNode(final List> dataNodes, final StringBuilder output) throws IOException, WriteException { + for (final Node dataNode : dataNodes) { + outFormatter.increaseIndent(); + outFormatter.addStringWithIndent(output, dataNode.getNodeType().getLocalName()); + if (dataNode instanceof CompositeNode) { + outFormatter.openComposite(output); + outFormatter.newLine(output); + writeNode(((CompositeNode) dataNode).getValue(), output); + outFormatter.closeCompositeWithIndent(output); + outFormatter.newLine(output); + } else if (dataNode instanceof SimpleNode) { + final SimpleNode simpleNode = (SimpleNode) dataNode; + output.append(" "); + output.append(simpleNode.getValue()); + outFormatter.newLine(output); + } + outFormatter.decreaseIndent(); + } + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/ContainerNodeCliSerializer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/ContainerNodeCliSerializer.java new file mode 100644 index 0000000000..62b995e937 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/ContainerNodeCliSerializer.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.ContainerNodeBaseSerializer; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; + +final class ContainerNodeCliSerializer extends ContainerNodeBaseSerializer { + + private final NodeSerializerDispatcher dispatcher; + private final OutFormatter out; + + ContainerNodeCliSerializer(final OutFormatter out, final NodeSerializerDispatcher dispatcher) { + this.out = Preconditions.checkNotNull(out); + this.dispatcher = Preconditions.checkNotNull(dispatcher); + } + + @Override + public Iterable serialize(final ContainerSchemaNode schema, final ContainerNode containerNode) { + final StringBuilder output = new StringBuilder(); + out.increaseIndent(); + out.addStringWithIndent(output, containerNode.getNodeType().getLocalName()); + out.openComposite(output); + out.newLine(output); + + for (final String childOutput : super.serialize(schema, containerNode)) { + output.append(childOutput); + out.newLine(output); + } + + out.closeCompositeWithIndent(output); + out.decreaseIndent(); + return Collections.singletonList(output.toString()); + } + + @Override + protected NodeSerializerDispatcher getNodeDispatcher() { + return dispatcher; + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafNodeCliSerializer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafNodeCliSerializer.java new file mode 100644 index 0000000000..d3216c26a9 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafNodeCliSerializer.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import com.google.common.base.Preconditions; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.LeafNodeBaseSerializer; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; + +final class LeafNodeCliSerializer extends LeafNodeBaseSerializer { + private final OutFormatter out; + + LeafNodeCliSerializer(final OutFormatter out) { + this.out = Preconditions.checkNotNull(out); + } + + @Override + public String serializeLeaf(final LeafSchemaNode schema, final LeafNode node) { + final StringBuilder output = new StringBuilder(); + out.increaseIndent(); + out.addStringWithIndent(output, node.getNodeType().getLocalName()); + output.append(" "); + output.append(node.getValue()); + out.decreaseIndent(); + return output.toString(); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafSetEntryNodeCliSerializer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafSetEntryNodeCliSerializer.java new file mode 100644 index 0000000000..4937432ebe --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafSetEntryNodeCliSerializer.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.LeafSetEntryNodeBaseSerializer; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; + +final class LeafSetEntryNodeCliSerializer extends LeafSetEntryNodeBaseSerializer { + + private final OutFormatter out; + + public LeafSetEntryNodeCliSerializer(final OutFormatter out) { + this.out = out; + } + + @Override + protected String serializeLeaf(final LeafListSchemaNode schema, final LeafSetEntryNode node) { + final StringBuilder output = new StringBuilder(); + out.increaseIndent(); + out.addStringWithIndent(output, node.getValue().toString()); + out.decreaseIndent(); + return output.toString(); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafSetNodeCliSerializer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafSetNodeCliSerializer.java new file mode 100644 index 0000000000..8d618e870b --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/LeafSetNodeCliSerializer.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import java.util.Collections; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializer; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; + +final class LeafSetNodeCliSerializer implements + FromNormalizedNodeSerializer, LeafListSchemaNode> { + private final LeafSetEntryNodeCliSerializer leafSetEntryNodeSerializer; + private final OutFormatter out; + + LeafSetNodeCliSerializer(final OutFormatter out, final LeafSetEntryNodeCliSerializer leafSetEntryNodeSerializer) { + this.out = out; + this.leafSetEntryNodeSerializer = leafSetEntryNodeSerializer; + } + + @Override + public Iterable serialize(final LeafListSchemaNode schema, final LeafSetNode node) { + final StringBuilder output = new StringBuilder(); + out.increaseIndent(); + out.addStringWithIndent(output, node.getNodeType().getLocalName()); + out.openComposite(output); + out.newLine(output); + for (final LeafSetEntryNode leafEntryNode : node.getValue()) { + final Iterable valueFromLeafSetEntry = leafSetEntryNodeSerializer.serialize(schema, leafEntryNode); + output.append(valueFromLeafSetEntry.iterator().next()); + out.newLine(output); + } + out.closeCompositeWithIndent(output); + out.decreaseIndent(); + return Collections.singletonList(output.toString()); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/MapEntryNodeCliSerializer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/MapEntryNodeCliSerializer.java new file mode 100644 index 0000000000..15f86a788b --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/MapEntryNodeCliSerializer.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.MapEntryNodeBaseSerializer; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +final class MapEntryNodeCliSerializer extends MapEntryNodeBaseSerializer { + + private final NodeSerializerDispatcher dispatcher; + private final OutFormatter out; + + MapEntryNodeCliSerializer(final OutFormatter out, final NodeSerializerDispatcher dispatcher) { + this.out = Preconditions.checkNotNull(out); + this.dispatcher = Preconditions.checkNotNull(dispatcher); + } + + @Override + public Iterable serialize(final ListSchemaNode schema, final MapEntryNode node) { + final StringBuilder output = new StringBuilder(); + out.increaseIndent(); + out.addStringWithIndent(output, node.getNodeType().getLocalName()); + serializeKeysIfPresent(node, output); + + out.openComposite(output); + out.newLine(output); + + for (final String childOutput : super.serialize(schema, node)) { + output.append(childOutput); + out.newLine(output); + } + + out.closeCompositeWithIndent(output); + out.newLine(output); + out.decreaseIndent(); + return Collections.singletonList(output.toString()); + } + + private void serializeKeysIfPresent(final MapEntryNode node, final StringBuilder output) { + final Map keyValues = node.getIdentifier().getKeyValues(); + if (keyValues.isEmpty()) { + return; + } + + int i = 0; + output.append(" ["); + for (final Entry qNameObjectEntry : keyValues.entrySet()) { + output.append(qNameObjectEntry.getKey().getLocalName()); + output.append("="); + output.append(qNameObjectEntry.getValue().toString()); + if (++i != keyValues.size()) { + output.append(", "); + } + } + output.append("]"); + } + + @Override + protected NodeSerializerDispatcher getNodeDispatcher() { + return dispatcher; + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/MapNodeCliSerializer.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/MapNodeCliSerializer.java new file mode 100644 index 0000000000..b08acbf38e --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/MapNodeCliSerializer.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import com.google.common.base.Preconditions; +import java.util.Collections; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializer; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +final class MapNodeCliSerializer implements FromNormalizedNodeSerializer { + + private final FromNormalizedNodeSerializer mapEntrySerializer; + private final OutFormatter out; + + MapNodeCliSerializer(final OutFormatter out, final MapEntryNodeCliSerializer mapEntrySerializer) { + this.out = Preconditions.checkNotNull(out); + this.mapEntrySerializer = mapEntrySerializer; + } + + @Override + public Iterable serialize(final ListSchemaNode schema, final MapNode node) { + final StringBuilder output = new StringBuilder(); + + out.increaseIndent(); + out.addStringWithIndent(output, node.getNodeType().getLocalName()); + output.append(" "); + out.openComposite(output); + out.newLine(output); + + for (final MapEntryNode mapEntryNode : node.getValue()) { + final Iterable valueFromLeafSetEntry = mapEntrySerializer.serialize(schema, mapEntryNode); + output.append(valueFromLeafSetEntry.iterator().next()); + } + + out.closeCompositeWithIndent(output); + out.decreaseIndent(); + + return Collections.singletonList(output.toString()); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/NodeCliSerializerDispatcher.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/NodeCliSerializerDispatcher.java new file mode 100644 index 0000000000..08abd47f25 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/NodeCliSerializerDispatcher.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2013 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.cli.writer.impl; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +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.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.MixinNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.FromNormalizedNodeSerializerFactory; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +public class NodeCliSerializerDispatcher implements NodeSerializerDispatcher { + private final FromNormalizedNodeSerializerFactory factory; + + public NodeCliSerializerDispatcher(final FromNormalizedNodeSerializerFactory factory) { + this.factory = Preconditions.checkNotNull(factory); + } + + @Override + public final Iterable dispatchChildElement(final Object childSchema, + final DataContainerChild dataContainerChild) { + if (dataContainerChild instanceof ContainerNode) { + return onContainerNode(childSchema, dataContainerChild); + } else if (dataContainerChild instanceof LeafNode) { + return onLeafNode(childSchema, dataContainerChild); + } else if (dataContainerChild instanceof MixinNode) { + if (dataContainerChild instanceof LeafSetNode) { + return onLeafListNode(childSchema, dataContainerChild); + } else if (dataContainerChild instanceof MapNode) { + return onListNode(childSchema, dataContainerChild); + } else if (dataContainerChild instanceof ChoiceNode) { + return onChoiceNode(childSchema, dataContainerChild); + } else if (dataContainerChild instanceof AugmentationNode) { + return onAugmentationSchema(childSchema, dataContainerChild); + } + } + throw new IllegalArgumentException("Unable to serialize " + childSchema); + } + + private Iterable onAugmentationSchema(final Object childSchema, + final DataContainerChild dataContainerChild) { + checkSchemaCompatibility(childSchema, AugmentationSchema.class, dataContainerChild); + return factory.getAugmentationNodeSerializer().serialize((AugmentationSchema) childSchema, + (AugmentationNode) dataContainerChild); + } + + private Iterable onChoiceNode(final Object childSchema, + final DataContainerChild dataContainerChild) { + checkSchemaCompatibility(childSchema, org.opendaylight.yangtools.yang.model.api.ChoiceNode.class, + dataContainerChild); + return factory.getChoiceNodeSerializer().serialize( + (org.opendaylight.yangtools.yang.model.api.ChoiceNode) childSchema, (ChoiceNode) dataContainerChild); + } + + private Iterable onListNode(final Object childSchema, + final DataContainerChild dataContainerChild) { + checkSchemaCompatibility(childSchema, ListSchemaNode.class, dataContainerChild); + return factory.getMapNodeSerializer().serialize((ListSchemaNode) childSchema, (MapNode) dataContainerChild); + } + + private Iterable onLeafListNode(final Object childSchema, + final DataContainerChild dataContainerChild) { + checkSchemaCompatibility(childSchema, LeafListSchemaNode.class, dataContainerChild); + return factory.getLeafSetNodeSerializer().serialize((LeafListSchemaNode) childSchema, + (LeafSetNode) dataContainerChild); + } + + private Iterable onLeafNode(final Object childSchema, + final DataContainerChild dataContainerChild) { + checkSchemaCompatibility(childSchema, LeafSchemaNode.class, dataContainerChild); + final Iterable elements = factory.getLeafNodeSerializer().serialize((LeafSchemaNode) childSchema, + (LeafNode) dataContainerChild); + checkOnlyOneSerializedElement(elements, dataContainerChild); + return elements; + } + + private static void checkOnlyOneSerializedElement(final Iterable elements, + final DataContainerChild dataContainerChild) { + final int size = Iterables.size(elements); + Preconditions.checkArgument(size == 1, + "Unexpected count of elements for entry serialized from: %s, should be 1, was: %s", dataContainerChild, + size); + } + + private Iterable onContainerNode(final Object childSchema, + final DataContainerChild dataContainerChild) { + checkSchemaCompatibility(childSchema, ContainerSchemaNode.class, dataContainerChild); + + final Iterable elements = factory.getContainerNodeSerializer().serialize( + (ContainerSchemaNode) childSchema, (ContainerNode) dataContainerChild); + checkOnlyOneSerializedElement(elements, dataContainerChild); + return elements; + } + + private static void checkSchemaCompatibility(final Object childSchema, final Class containerSchemaNodeClass, + final DataContainerChild dataContainerChild) { + Preconditions.checkArgument(containerSchemaNodeClass.isAssignableFrom(childSchema.getClass()), + "Incompatible schema: %s with node: %s, expected: %s", childSchema, dataContainerChild, + containerSchemaNodeClass); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/NormalizedNodeWriter.java b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/NormalizedNodeWriter.java new file mode 100644 index 0000000000..eef9a39a27 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/java/org/opendaylight/controller/netconf/cli/writer/impl/NormalizedNodeWriter.java @@ -0,0 +1,94 @@ +/* + * 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.netconf.cli.writer.impl; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import org.opendaylight.controller.netconf.cli.io.ConsoleIO; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.controller.netconf.cli.writer.WriteException; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer.NodeSerializerDispatcher; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils; +import org.opendaylight.yangtools.yang.data.json.schema.cnsn.parser.CnSnToNormalizedNodeParserFactory; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NormalizedNodeWriter extends AbstractWriter { + + private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeWriter.class); + private final OutFormatter out; + + public NormalizedNodeWriter(final ConsoleIO console, final OutFormatter out) { + super(console); + this.out = out; + } + + public void writeInner(final DataSchemaNode dataSchemaNode, final List> dataNodes) throws WriteException, + IOException { + + // TODO - add getDispatcher method to CnSnToNormalizedNodeParserFactory + // to be able call dispatchChildElement + final DataContainerChild dataContainerChild = parseToNormalizedNode(dataNodes, + dataSchemaNode); + + if (dataContainerChild != null) { + console.writeLn(serializeToCliOutput(dataContainerChild, dataSchemaNode)); + } + + } + + private String serializeToCliOutput(final DataContainerChild dataContainerChild, + final DataSchemaNode childSchema) { + final CliOutputFromNormalizedNodeSerializerFactory factorySerialization = CliOutputFromNormalizedNodeSerializerFactory + .getInstance(out, DomUtils.defaultValueCodecProvider()); + final NodeSerializerDispatcher dispatcher = factorySerialization.getDispatcher(); + final Iterable result = dispatcher.dispatchChildElement(childSchema, dataContainerChild); + + if (result == null) { + return ""; + } + + final Iterator output = result.iterator(); + if (!output.hasNext()) { + return ""; + } + + return output.next(); + } + + private DataContainerChild parseToNormalizedNode(final List> dataNodes, + final DataSchemaNode dataSchemaNode) { + final CnSnToNormalizedNodeParserFactory factoryParsing = CnSnToNormalizedNodeParserFactory.getInstance(); + if (dataSchemaNode instanceof ContainerSchemaNode) { + return factoryParsing.getContainerNodeParser().parse(dataNodes, (ContainerSchemaNode) dataSchemaNode); + } else if (dataSchemaNode instanceof LeafSchemaNode) { + return factoryParsing.getLeafNodeParser().parse(dataNodes, (LeafSchemaNode) dataSchemaNode); + } else if (dataSchemaNode instanceof LeafListSchemaNode) { + return factoryParsing.getLeafSetNodeParser().parse(dataNodes, (LeafListSchemaNode) dataSchemaNode); + } else if (dataSchemaNode instanceof ListSchemaNode) { + return factoryParsing.getMapNodeParser().parse(dataNodes, (ListSchemaNode) dataSchemaNode); + } else if (dataSchemaNode instanceof ChoiceNode) { + return factoryParsing.getChoiceNodeParser().parse(dataNodes, (ChoiceNode) dataSchemaNode); + } else if (dataSchemaNode instanceof AugmentationSchema) { + return factoryParsing.getAugmentationNodeParser().parse(dataNodes, (AugmentationSchema) dataSchemaNode); + } + return null; + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/main/resources/logback.xml b/opendaylight/netconf/netconf-cli/src/main/resources/logback.xml new file mode 100644 index 0000000000..55eedc5afc --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/resources/logback.xml @@ -0,0 +1,31 @@ + + + + netconfcli.log + + + %date{"yyyy-MM-dd HH:mm:ss.SSS z"} [%thread] %-5level + %logger{35} - %msg%n + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + diff --git a/opendaylight/netconf/netconf-cli/src/main/resources/schema/common/ietf-inet-types.yang b/opendaylight/netconf/netconf-cli/src/main/resources/schema/common/ietf-inet-types.yang new file mode 100644 index 0000000000..edd285d26c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/resources/schema/common/ietf-inet-types.yang @@ -0,0 +1,427 @@ +module ietf-inet-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types"; + prefix "inet"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Partain + + + WG Chair: David Kessens + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types for Internet addresses and related things. + + Copyright (c) 2010 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in Section + 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6021; see + the RFC itself for full legal notices."; + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of protocol field related types ***/ + + typedef ip-version { + type enumeration { + enum unknown { + value "0"; + description + "An unknown or unspecified version of the Internet protocol."; + } + enum ipv4 { + value "1"; + description + "The IPv4 protocol as defined in RFC 791."; + } + enum ipv6 { + value "2"; + description + "The IPv6 protocol as defined in RFC 2460."; + } + } + description + "This value represents the version of the IP protocol. + + In the value set and its semantics, this type is equivalent + to the InetVersion textual convention of the SMIv2."; + reference + "RFC 791: Internet Protocol + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef dscp { + type uint8 { + range "0..63"; + } + description + "The dscp type represents a Differentiated Services Code-Point + that may be used for marking packets in a traffic stream. + + In the value set and its semantics, this type is equivalent + to the Dscp textual convention of the SMIv2."; + reference + "RFC 3289: Management Information Base for the Differentiated + Services Architecture + RFC 2474: Definition of the Differentiated Services Field + (DS Field) in the IPv4 and IPv6 Headers + RFC 2780: IANA Allocation Guidelines For Values In + the Internet Protocol and Related Headers"; + } + + typedef ipv6-flow-label { + type uint32 { + range "0..1048575"; + } + description + "The flow-label type represents flow identifier or Flow Label + in an IPv6 packet header that may be used to discriminate + traffic flows. + + In the value set and its semantics, this type is equivalent + to the IPv6FlowLabel textual convention of the SMIv2."; + reference + "RFC 3595: Textual Conventions for IPv6 Flow Label + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification"; + } + + typedef port-number { + type uint16 { + range "0..65535"; + } + description + "The port-number type represents a 16-bit port number of an + Internet transport layer protocol such as UDP, TCP, DCCP, or + SCTP. Port numbers are assigned by IANA. A current list of + all assignments is available from . + + Note that the port number value zero is reserved by IANA. In + situations where the value zero does not make sense, it can + be excluded by subtyping the port-number type. + + In the value set and its semantics, this type is equivalent + to the InetPortNumber textual convention of the SMIv2."; + reference + "RFC 768: User Datagram Protocol + RFC 793: Transmission Control Protocol + RFC 4960: Stream Control Transmission Protocol + RFC 4340: Datagram Congestion Control Protocol (DCCP) + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + /*** collection of autonomous system related types ***/ + + typedef as-number { + type uint32; + description + "The as-number type represents autonomous system numbers + which identify an Autonomous System (AS). An AS is a set + of routers under a single technical administration, using + an interior gateway protocol and common metrics to route + packets within the AS, and using an exterior gateway + protocol to route packets to other ASs'. IANA maintains + the AS number space and has delegated large parts to the + regional registries. + + Autonomous system numbers were originally limited to 16 + bits. BGP extensions have enlarged the autonomous system + number space to 32 bits. This type therefore uses an uint32 + base type without a range restriction in order to support + a larger autonomous system number space. + + In the value set and its semantics, this type is equivalent + to the InetAutonomousSystemNumber textual convention of + the SMIv2."; + reference + "RFC 1930: Guidelines for creation, selection, and registration + of an Autonomous System (AS) + RFC 4271: A Border Gateway Protocol 4 (BGP-4) + RFC 4893: BGP Support for Four-octet AS Number Space + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + /*** collection of IP address and hostname related types ***/ + + typedef ip-address { + type union { + type inet:ipv4-address; + type inet:ipv6-address; + } + description + "The ip-address type represents an IP address and is IP + version neutral. The format of the textual representations + implies the IP version."; + } + + typedef ipv4-address { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '(%[\p{N}\p{L}]+)?'; + } + description + "The ipv4-address type represents an IPv4 address in + dotted-quad notation. The IPv4 address may include a zone + index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format for the zone index is the numerical + format"; + } + + typedef ipv6-address { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(%[\p{N}\p{L}]+)?'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(%.+)?'; + } + description + "The ipv6-address type represents an IPv6 address in full, + mixed, shortened, and shortened-mixed notation. The IPv6 + address may include a zone index, separated by a % sign. + + + + + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format of IPv6 addresses uses the compressed + format described in RFC 4291, Section 2.2, item 2 with the + following additional rules: the :: substitution must be + applied to the longest sequence of all-zero 16-bit chunks + in an IPv6 address. If there is a tie, the first sequence + of all-zero 16-bit chunks is replaced by ::. Single + all-zero 16-bit chunks are not compressed. The canonical + format uses lowercase characters and leading zeros are + not allowed. The canonical format for the zone index is + the numerical format as described in RFC 4007, Section + 11.2."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text Representation"; + } + + typedef ip-prefix { + type union { + type inet:ipv4-prefix; + type inet:ipv6-prefix; + } + description + "The ip-prefix type represents an IP prefix and is IP + version neutral. The format of the textual representations + implies the IP version."; + } + + typedef ipv4-prefix { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '/(([0-9])|([1-2][0-9])|(3[0-2]))'; + } + description + "The ipv4-prefix type represents an IPv4 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 32. + + + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The canonical format of an IPv4 prefix has all bits of + the IPv4 address set to zero that are not part of the + IPv4 prefix."; + } + + typedef ipv6-prefix { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(/.+)'; + } + description + "The ipv6-prefix type represents an IPv6 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal 128. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The IPv6 address should have all bits that do not belong + to the prefix set to zero. + + The canonical format of an IPv6 prefix has all bits of + the IPv6 address set to zero that are not part of the + IPv6 prefix. Furthermore, IPv6 address is represented + in the compressed format described in RFC 4291, Section + 2.2, item 2 with the following additional rules: the :: + substitution must be applied to the longest sequence of + all-zero 16-bit chunks in an IPv6 address. If there is + a tie, the first sequence of all-zero 16-bit chunks is + replaced by ::. Single all-zero 16-bit chunks are not + compressed. The canonical format uses lowercase + characters and leading zeros are not allowed."; + reference + "RFC 4291: IP Version 6 Addressing Architecture"; + } + + + /*** collection of domain name and URI types ***/ + + typedef domain-name { + type string { + pattern '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*' + + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)' + + '|\.'; + length "1..253"; + } + description + "The domain-name type represents a DNS domain name. The + name SHOULD be fully qualified whenever possible. + + Internet domain names are only loosely specified. Section + 3.5 of RFC 1034 recommends a syntax (modified in Section + 2.1 of RFC 1123). The pattern above is intended to allow + for current practice in domain name use, and some possible + future expansion. It is designed to hold various types of + domain names, including names used for A or AAAA records + (host names) and other records, such as SRV records. Note + that Internet host names have a stricter syntax (described + in RFC 952) than the DNS recommendations in RFCs 1034 and + 1123, and that systems that want to store host names in + schema nodes using the domain-name type are recommended to + adhere to this stricter standard to ensure interoperability. + + The encoding of DNS names in the DNS protocol is limited + to 255 characters. Since the encoding consists of labels + prefixed by a length bytes and there is a trailing NULL + byte, only 253 characters can appear in the textual dotted + notation. + + The description clause of schema nodes using the domain-name + type MUST describe when and how these names are resolved to + IP addresses. Note that the resolution of a domain-name value + may require to query multiple DNS records (e.g., A for IPv4 + and AAAA for IPv6). The order of the resolution process and + which DNS record takes precedence can either be defined + explicitely or it may depend on the configuration of the + resolver. + + Domain-name values use the US-ASCII encoding. Their canonical + format uses lowercase US-ASCII characters. Internationalized + domain names MUST be encoded in punycode as described in RFC + 3492"; + reference + "RFC 952: DoD Internet Host Table Specification + RFC 1034: Domain Names - Concepts and Facilities + RFC 1123: Requirements for Internet Hosts -- Application + and Support + RFC 2782: A DNS RR for specifying the location of services + (DNS SRV) + RFC 3492: Punycode: A Bootstring encoding of Unicode for + Internationalized Domain Names in Applications + (IDNA) + RFC 5891: Internationalizing Domain Names in Applications + (IDNA): Protocol"; + } + + typedef host { + type union { + type inet:ip-address; + type inet:domain-name; + } + description + "The host type represents either an IP address or a DNS + domain name."; + } + + typedef uri { + type string; + description + "The uri type represents a Uniform Resource Identifier + (URI) as defined by STD 66. + + Objects using the uri type MUST be in US-ASCII encoding, + and MUST be normalized as described by RFC 3986 Sections + 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary + percent-encoding is removed, and all case-insensitive + characters are set to lowercase except for hexadecimal + digits, which are normalized to uppercase as described in + Section 6.2.2.1. + + The purpose of this normalization is to help provide + unique URIs. Note that this normalization is not + sufficient to provide uniqueness. Two URIs that are + textually distinct after this normalization may still be + equivalent. + + Objects using the uri type may restrict the schemes that + they permit. For example, 'data:' and 'urn:' schemes + might not be appropriate. + + A zero-length URI is not a valid URI. This can be used to + express 'URI absent' where required. + + In the value set and its semantics, this type is equivalent + to the Uri SMIv2 textual convention defined in RFC 5017."; + reference + "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax + RFC 3305: Report from the Joint W3C/IETF URI Planning Interest + Group: Uniform Resource Identifiers (URIs), URLs, + and Uniform Resource Names (URNs): Clarifications + and Recommendations + RFC 5017: MIB Textual Conventions for Uniform Resource + Identifiers (URIs)"; + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/resources/schema/common/netconf-cli-ext.yang b/opendaylight/netconf/netconf-cli/src/main/resources/schema/common/netconf-cli-ext.yang new file mode 100644 index 0000000000..139778a8d1 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/resources/schema/common/netconf-cli-ext.yang @@ -0,0 +1,18 @@ +module netconf-cli-ext { + + namespace "urn:ietf:params:xml:ns:netconf:base:1.0:cli"; + + prefix cliext; + + revision 2014-05-26 { + description + "Initial revision"; + } + + extension argument-handler { + description + "Links custom argument reader to an input argument"; + argument "name"; + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/resources/schema/local/netconf-cli.yang b/opendaylight/netconf/netconf-cli/src/main/resources/schema/local/netconf-cli.yang new file mode 100644 index 0000000000..52f7b970eb --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/resources/schema/local/netconf-cli.yang @@ -0,0 +1,97 @@ +module netconf-cli { + + namespace "netconf:cli"; + prefix ncli; + + import ietf-inet-types { prefix inet; revision-date 2010-09-24; } + import netconf-cli-ext { prefix cliext; revision-date 2014-05-26; } + + + revision 2014-05-22 { + description + "Initial revision."; + } + + extension java-class { + description + "This could be used to link between rpc yang definition and custom command implementation"; + + argument "name"; + } + + rpc help { + description + "Display help"; + + output { + list commands { + + key "id"; + leaf id { + type string; + } + leaf description { + type string; + } + } + } + } + + rpc close { + description + "Close the whole cli"; + } + + rpc connect { + + description + "Connect to a remote netconf device, if not connected yet. Connection initialization is blocking and might take some time, depending on amount of yang schemas in remote device."; + + input { + + // TODO yangtools keep input arguments unordered so the ordering in cli is random + leaf address-name { + type inet:host; + default localhost; + } + + leaf address-port { + type inet:port-number; + default 830; + } + + leaf user-name { + type string; + } + + leaf user-password { + cliext:argument-handler org.opendaylight.controller.netconf.cli.reader.custom.PasswordReader; + type string; + } + } + + output { + leaf status { + type string; + } + + leaf-list remote-commands { + type string; + } + } + } + + + rpc disconnect { + + description + "Disconnect from a netconf device that is currently connected"; + + output { + leaf status { + type string; + } + } + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/main/resources/schema/remote/ietf-netconf.yang b/opendaylight/netconf/netconf-cli/src/main/resources/schema/remote/ietf-netconf.yang new file mode 100644 index 0000000000..d41c2e3dd1 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/main/resources/schema/remote/ietf-netconf.yang @@ -0,0 +1,943 @@ +module ietf-netconf { + + // the namespace for NETCONF XML definitions is unchanged + // from RFC 4741, which this document replaces + namespace "urn:ietf:params:xml:ns:netconf:base:1.0"; + + prefix nc; + + import ietf-inet-types { + prefix inet; + } + + import netconf-cli-ext { prefix cliext; revision-date 2014-05-26; } + + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: Bert Wijnen + + + WG Chair: Mehmet Ersue + + + Editor: Martin Bjorklund + + + Editor: Juergen Schoenwaelder + + + Editor: Andy Bierman + "; + description + "NETCONF Protocol Data Types and Protocol Operations. + + Copyright (c) 2011 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6241; see + the RFC itself for full legal notices."; + revision 2011-06-01 { + description + "Initial revision"; + reference + "RFC 6241: Network Configuration Protocol"; + } + + extension get-filter-element-attributes { + description + "If this extension is present within an 'anyxml' + statement named 'filter', which must be conceptually + defined within the RPC input section for the + and protocol operations, then the + following unqualified XML attribute is supported + within the element, within a or + protocol operation: + + type : optional attribute with allowed + value strings 'subtree' and 'xpath'. + If missing, the default value is 'subtree'. + + If the 'xpath' feature is supported, then the + following unqualified XML attribute is + also supported: + + select: optional attribute containing a + string representing an XPath expression. + The 'type' attribute must be equal to 'xpath' + if this attribute is present."; + } + + // NETCONF capabilities defined as features + feature writable-running { + description + "NETCONF :writable-running capability; + If the server advertises the :writable-running + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.2"; + } + + feature candidate { + description + "NETCONF :candidate capability; + If the server advertises the :candidate + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.3"; + } + + feature confirmed-commit { + if-feature candidate; + description + "NETCONF :confirmed-commit:1.1 capability; + If the server advertises the :confirmed-commit:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + + reference "RFC 6241, Section 8.4"; + } + + feature rollback-on-error { + description + "NETCONF :rollback-on-error capability; + If the server advertises the :rollback-on-error + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.5"; + } + + feature validate { + description + "NETCONF :validate:1.1 capability; + If the server advertises the :validate:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.6"; + } + + feature startup { + description + "NETCONF :startup capability; + If the server advertises the :startup + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.7"; + } + + feature url { + description + "NETCONF :url capability; + If the server advertises the :url + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.8"; + } + + feature xpath { + description + "NETCONF :xpath capability; + If the server advertises the :xpath + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.9"; + } + + // NETCONF Simple Types + + typedef session-id-type { + type uint32 { + range "1..max"; + } + description + "NETCONF Session Id"; + } + + typedef session-id-or-zero-type { + type uint32; + description + "NETCONF Session Id or Zero to indicate none"; + } + typedef error-tag-type { + type enumeration { + enum in-use { + description + "The request requires a resource that + already is in use."; + } + enum invalid-value { + description + "The request specifies an unacceptable value for one + or more parameters."; + } + enum too-big { + description + "The request or response (that would be generated) is + too large for the implementation to handle."; + } + enum missing-attribute { + description + "An expected attribute is missing."; + } + enum bad-attribute { + description + "An attribute value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-attribute { + description + "An unexpected attribute is present."; + } + enum missing-element { + description + "An expected element is missing."; + } + enum bad-element { + description + "An element value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-element { + description + "An unexpected element is present."; + } + enum unknown-namespace { + description + "An unexpected namespace is present."; + } + enum access-denied { + description + "Access to the requested protocol operation or + data model is denied because authorization failed."; + } + enum lock-denied { + description + "Access to the requested lock is denied because the + lock is currently held by another entity."; + } + enum resource-denied { + description + "Request could not be completed because of + insufficient resources."; + } + enum rollback-failed { + description + "Request to roll back some configuration change (via + rollback-on-error or operations) + was not completed for some reason."; + + } + enum data-exists { + description + "Request could not be completed because the relevant + data model content already exists. For example, + a 'create' operation was attempted on data that + already exists."; + } + enum data-missing { + description + "Request could not be completed because the relevant + data model content does not exist. For example, + a 'delete' operation was attempted on + data that does not exist."; + } + enum operation-not-supported { + description + "Request could not be completed because the requested + operation is not supported by this implementation."; + } + enum operation-failed { + description + "Request could not be completed because the requested + operation failed for some reason not covered by + any other error condition."; + } + enum partial-operation { + description + "This error-tag is obsolete, and SHOULD NOT be sent + by servers conforming to this document."; + } + enum malformed-message { + description + "A message could not be handled because it failed to + be parsed correctly. For example, the message is not + well-formed XML or it uses an invalid character set."; + } + } + description "NETCONF Error Tag"; + reference "RFC 6241, Appendix A"; + } + + typedef error-severity-type { + type enumeration { + enum error { + description "Error severity"; + } + enum warning { + description "Warning severity"; + } + } + description "NETCONF Error Severity"; + reference "RFC 6241, Section 4.3"; + } + + typedef edit-operation-type { + type enumeration { + enum merge { + description + "The configuration data identified by the + element containing this attribute is merged + with the configuration at the corresponding + level in the configuration datastore identified + by the target parameter."; + } + enum replace { + description + "The configuration data identified by the element + containing this attribute replaces any related + configuration in the configuration datastore + identified by the target parameter. If no such + configuration data exists in the configuration + datastore, it is created. Unlike a + operation, which replaces the + entire target configuration, only the configuration + actually present in the config parameter is affected."; + } + enum create { + description + "The configuration data identified by the element + containing this attribute is added to the + configuration if and only if the configuration + data does not already exist in the configuration + datastore. If the configuration data exists, an + element is returned with an + value of 'data-exists'."; + } + enum delete { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if and only if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, an element is returned with + an value of 'data-missing'."; + } + enum remove { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, the 'remove' operation is silently ignored + by the server."; + } + } + default "merge"; + description "NETCONF 'operation' attribute values"; + reference "RFC 6241, Section 7.2"; + } + + // NETCONF Standard Protocol Operations + + rpc get-config { + description + "Retrieve all or part of a specified configuration."; + + reference "RFC 6241, Section 7.1"; + + input { + container source { + description + "Particular configuration to retrieve."; + + choice config-source { + mandatory true; + description + "The configuration to retrieve."; + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source. + This is optional-to-implement on the server because + not all servers will support filtering for this + datastore."; + } + } + } + + anyxml filter { + description + "Subtree or XPath filter to use."; + nc:get-filter-element-attributes; + // TODO this extension should be augmented (anyxml nodes cannot be augmented) + // or we can identify custom input/output arguments by schemaPath defined for custom handlers + cliext:argument-handler org.opendaylight.controller.netconf.cli.reader.custom.FilterReader; + } + } + + output { + anyxml data { + description + "Copy of the source datastore subset that matched + the filter criteria (if any). An empty data container + indicates that the request did not produce any results."; + cliext:argument-handler org.opendaylight.controller.netconf.cli.writer.custom.DataWriter; + } + } + } + + rpc edit-config { + description + "The operation loads all or part of a specified + configuration to the specified target configuration."; + + reference "RFC 6241, Section 7.2"; + + input { + container target { + description + "Particular configuration to edit."; + + choice config-target { + mandatory true; + description + "The configuration target."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config source."; + } + } + } + + leaf default-operation { + type enumeration { + enum merge { + description + "The default operation is merge."; + } + enum replace { + description + "The default operation is replace."; + } + enum none { + description + "There is no default operation."; + } + } + default "merge"; + description + "The default operation to use."; + } + + leaf test-option { + if-feature validate; + type enumeration { + enum test-then-set { + description + "The server will test and then set if no errors."; + } + enum set { + description + "The server will set without a test first."; + } + + enum test-only { + description + "The server will only test and not set, even + if there are no errors."; + } + } + default "test-then-set"; + description + "The test option to use."; + } + + leaf error-option { + type enumeration { + enum stop-on-error { + description + "The server will stop on errors."; + } + enum continue-on-error { + description + "The server may continue on errors."; + } + enum rollback-on-error { + description + "The server will roll back on errors. + This value can only be used if the 'rollback-on-error' + feature is supported."; + } + } + default "stop-on-error"; + description + "The error option to use."; + } + + choice edit-content { + mandatory true; + description + "The content for the edit operation."; + + cliext:argument-handler org.opendaylight.controller.netconf.cli.reader.custom.EditContentReader; + + anyxml config { + description + "Inline Config content."; + cliext:argument-handler org.opendaylight.controller.netconf.cli.reader.custom.ConfigReader; + } + + leaf url { + if-feature url; + type inet:uri; + description + "URL-based config content."; + } + } + } + } + + rpc copy-config { + description + "Create or replace an entire configuration datastore with the + contents of another complete configuration datastore."; + + reference "RFC 6241, Section 7.3"; + + input { + container target { + description + "Particular configuration to copy to."; + + choice config-target { + mandatory true; + description + "The configuration target of the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config target. + This is optional-to-implement on the server."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + + container source { + description + "Particular configuration to copy from."; + + choice config-source { + mandatory true; + description + "The configuration source for the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } + + rpc delete-config { + description + "Delete a configuration datastore."; + + reference "RFC 6241, Section 7.4"; + + input { + container target { + description + "Particular configuration to delete."; + + choice config-target { + mandatory true; + description + "The configuration target to delete."; + + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + } + } + + rpc lock { + description + "The lock operation allows the client to lock the configuration + system of a device."; + + reference "RFC 6241, Section 7.5"; + + input { + container target { + description + "Particular configuration to lock."; + + choice config-target { + mandatory true; + description + "The configuration target to lock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc unlock { + description + "The unlock operation is used to release a configuration lock, + previously obtained with the 'lock' operation."; + + reference "RFC 6241, Section 7.6"; + + input { + container target { + description + "Particular configuration to unlock."; + + choice config-target { + mandatory true; + description + "The configuration target to unlock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc get { + description + "Retrieve running configuration and device state information."; + + reference "RFC 6241, Section 7.7"; + + input { + anyxml filter { + description + "This parameter specifies the portion of the system + configuration and state data to retrieve."; + nc:get-filter-element-attributes; + // TODO this extension should be augmented (anyxml nodes cannot be augmented) + cliext:argument-handler org.opendaylight.controller.netconf.cli.reader.custom.FilterReader; + } + } + + output { + anyxml data { + description + "Copy of the running datastore subset and/or state + data that matched the filter criteria (if any). + An empty data container indicates that the request did not + produce any results."; + + cliext:argument-handler org.opendaylight.controller.netconf.cli.writer.custom.DataWriter; + + } + } + } + + rpc close-session { + description + "Request graceful termination of a NETCONF session."; + + reference "RFC 6241, Section 7.8"; + } + + rpc kill-session { + description + "Force the termination of a NETCONF session."; + + reference "RFC 6241, Section 7.9"; + + input { + leaf session-id { + type session-id-type; + mandatory true; + description + "Particular session to kill."; + } + } + } + + rpc commit { + if-feature candidate; + + description + "Commit the candidate configuration as the device's new + current configuration."; + + reference "RFC 6241, Section 8.3.4.1"; + + input { + leaf confirmed { + if-feature confirmed-commit; + type empty; + description + "Requests a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf confirm-timeout { + if-feature confirmed-commit; + type uint32 { + range "1..max"; + } + units "seconds"; + default "600"; // 10 minutes + description + "The timeout interval for a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist { + if-feature confirmed-commit; + type string; + description + "This parameter is used to make a confirmed commit + persistent. A persistent confirmed commit is not aborted + if the NETCONF session terminates. The only way to abort + a persistent confirmed commit is to let the timer expire, + or to use the operation. + + The value of this parameter is a token that must be given + in the 'persist-id' parameter of or + operations in order to confirm or cancel + the persistent confirmed commit. + + The token should be a random string."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist-id { + if-feature confirmed-commit; + type string; + description + "This parameter is given in order to commit a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + reference "RFC 6241, Section 8.3.4.1"; + } + + } + } + + rpc discard-changes { + if-feature candidate; + + description + "Revert the candidate configuration to the current + running configuration."; + reference "RFC 6241, Section 8.3.4.2"; + } + + rpc cancel-commit { + if-feature confirmed-commit; + description + "This operation is used to cancel an ongoing confirmed commit. + If the confirmed commit is persistent, the parameter + 'persist-id' must be given, and it must match the value of the + 'persist' parameter."; + reference "RFC 6241, Section 8.4.4.1"; + + input { + leaf persist-id { + type string; + description + "This parameter is given in order to cancel a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + } + } + } + + rpc validate { + if-feature validate; + + description + "Validates the contents of the specified configuration."; + + reference "RFC 6241, Section 8.6.4.1"; + + input { + container source { + description + "Particular configuration to validate."; + + choice config-source { + mandatory true; + description + "The configuration source to validate."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/ConsoleIOTestImpl.java b/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/ConsoleIOTestImpl.java new file mode 100644 index 0000000000..29d7abd85a --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/ConsoleIOTestImpl.java @@ -0,0 +1,85 @@ +/* + * 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.netconf.cli; + +import java.io.IOException; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.opendaylight.controller.netconf.cli.io.ConsoleIOImpl; + +public class ConsoleIOTestImpl extends ConsoleIOImpl { + + Map> inputValues = new HashMap<>(); + String lastMessage; + private final List valuesForMessages; + + public ConsoleIOTestImpl(final Map> inputValues, final List valuesForMessages) + throws IOException { + super(); + this.inputValues = inputValues; + this.valuesForMessages = valuesForMessages; + } + + StringBuilder output = new StringBuilder(); + + @Override + public String read() throws IOException { + final String prompt = buildPrompt(); + output.append(prompt); + System.out.print(prompt); + + String value = inputValues.get(prompt).pollFirst(); + if (value == null) { + value = getValueForLastMessage(); + } + + value = value != null ? value : "****NO VALUE****"; + + output.append(value + "\n"); + System.out.print(value + "\n"); + return value; + } + + private String getValueForLastMessage() { + for (final ValueForMessage valueForMessage : valuesForMessages) { + if (containsLastMessageKeyWords(valueForMessage.getKeyWords())) { + return valueForMessage.getValue(); + } + } + return null; + } + + private boolean containsLastMessageKeyWords(final List keyWords) { + for (final String keyWord : keyWords) { + if (!lastMessage.contains(keyWord)) { + return false; + } + } + return true; + } + + @Override + public void write(final CharSequence data) throws IOException { + output.append(data); + lastMessage = (String) data; + System.out.print(data); + } + + @Override + public void writeLn(final CharSequence data) throws IOException { + write(data); + output.append("\n"); + System.out.print("\n"); + } + + public String getConsoleOutput() { + return output.toString(); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/NetconfCliTest.java b/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/NetconfCliTest.java new file mode 100644 index 0000000000..7d85aa4f28 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/NetconfCliTest.java @@ -0,0 +1,145 @@ +/* + * 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.netconf.cli; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.opendaylight.controller.netconf.cli.io.IOUtil.PROMPT_SUFIX; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Ignore; +import org.junit.Test; +import org.opendaylight.controller.netconf.cli.reader.ReadingException; +import org.opendaylight.controller.netconf.cli.reader.impl.GenericReader; +import org.opendaylight.controller.netconf.cli.writer.OutFormatter; +import org.opendaylight.controller.netconf.cli.writer.WriteException; +import org.opendaylight.controller.netconf.cli.writer.impl.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.parser.api.YangContextParser; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; + +@Ignore +public class NetconfCliTest { + + private final static YangContextParser parser = new YangParserImpl(); + + private static SchemaContext loadSchemaContext(final String resourceDirectory) throws IOException, + URISyntaxException { + final URI uri = NetconfCliTest.class.getResource(resourceDirectory).toURI(); + final File testDir = new File(uri); + final String[] fileList = testDir.list(); + final List testFiles = new ArrayList(); + if (fileList == null) { + throw new FileNotFoundException(resourceDirectory); + } + for (final String fileName : fileList) { + if (new File(testDir, fileName).isDirectory() == false) { + testFiles.add(new File(testDir, fileName)); + } + } + return parser.parseFiles(testFiles); + } + + // @Ignore + @Test + public void cliTest() throws ReadingException, IOException, WriteException, URISyntaxException { + + final SchemaContext schemaContext = loadSchemaContext("/schema-context"); + assertNotNull(schemaContext); + + final DataSchemaNode cont1 = findTopLevelElement("ns:model1", "2014-05-14", "cont1", schemaContext); + final Map> values = new HashMap<>(); + + values.put(prompt("/cont1/cont11/lst111/[entry]/lf1111"), value("55", "32")); + values.put(prompt("/cont1/cont11/lst111/[entry]"), value("Y", "Y")); + values.put(prompt("/cont1/cont11/lst111/[entry]/cont111/lf1112"), + value("value for lf1112", "2value for lf1112")); + values.put(prompt("/cont1/cont11/lst111/[entry]/cont111/lflst1111"), value("Y", "N", "Y", "N")); + values.put(prompt("/cont1/cont11/lst111/[entry]/cont111/lflst1111/[entry]"), value("10", "15", "20", "30")); + + values.put(prompt("/cont1/cont11/lst111"), value("Y", "N")); + + values.put(prompt("/cont1/cont12/chcA"), value("AB")); + values.put(prompt("/cont1/cont12/chcA/cont12AB1/lf12AB1"), value("value for lf12AB1")); + + values.put(prompt("/cont1/cont12/lst121/[entry]/lf1211"), value("value for lf12112", "2value for lf12112")); + values.put(prompt("/cont1/cont12/lst121/[entry]"), value("Y", "Y")); + values.put(prompt("/cont1/cont12/lst121/[entry]/lst1211"), value("Y", "N", "Y", "N")); + values.put(prompt("/cont1/cont12/lst121/[entry]/lst1211/[entry]"), value("Y", "Y", "Y", "Y")); + values.put(prompt("/cont1/cont12/lst121/[entry]/lst1211/[entry]/lf12111"), value("5", "10", "21", "50")); + values.put(prompt("/cont1/cont12/lst121/[entry]/lst1211/[entry]/lf12112"), + value("value for lf12112", "2value for lf12112", "3value for lf12112", "4value for lf12112")); + + values.put(prompt("/cont1/cont12/lst121"), value("Y", "N")); + + values.put(prompt("/cont1/cont12/lst122"), value("Y", "N")); + + values.put(prompt("/cont1/lst11"), value("Y", "Y", "N")); + values.put(prompt("/cont1/lst11/[entry]"), value("Y", "Y", "Y")); + values.put(prompt("/cont1/lst11/[entry]/lf111"), + value("1value for lf111", "2value for lf111", "3value for lf111")); + + values.put(prompt("/cont1/cont12/data"), value("valuevalue1")); + + final List valuesForMessages = new ArrayList<>(); + valuesForMessages.add(new ValueForMessage("Y", "lst111", "[Y|N]")); + valuesForMessages.add(new ValueForMessage("Y", "lst121", "[Y|N]")); + valuesForMessages.add(new ValueForMessage("Y", "lst11", "[Y|N]")); + + final ConsoleIOTestImpl console = new ConsoleIOTestImpl(values, valuesForMessages); + + final List> redData = new GenericReader(console, new CommandArgHandlerRegistry(console, + new SchemaContextRegistry(schemaContext)), schemaContext).read(cont1); + assertNotNull(redData); + assertEquals(1, redData.size()); + + assertTrue(redData.get(0) instanceof CompositeNode); + final CompositeNode redTopLevelNode = (CompositeNode) redData.get(0); + + System.out.println("============================"); + new NormalizedNodeWriter(console, new OutFormatter()).write(cont1, redData); + + } + + private Deque value(final String... values) { + return new ArrayDeque<>(Arrays.asList(values)); + } + + private String prompt(final String path) { + return "/localhost" + path + PROMPT_SUFIX; + } + + private DataSchemaNode findTopLevelElement(final String namespace, final String revision, + final String topLevelElement, final SchemaContext schemaContext) { + final QName requiredElement = QName.create(namespace, revision, topLevelElement); + for (final DataSchemaNode dataSchemaNode : schemaContext.getChildNodes()) { + if (dataSchemaNode.getQName().equals(requiredElement)) { + return dataSchemaNode; + } + } + return null; + + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/ValueForMessages.java b/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/ValueForMessages.java new file mode 100644 index 0000000000..2532b36f52 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/ValueForMessages.java @@ -0,0 +1,29 @@ +/* + * 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.netconf.cli; + +import java.util.Arrays; +import java.util.List; + +class ValueForMessage { + List messageKeyWords; + String value; + + public ValueForMessage(final String value, final String... messageKeyWords) { + this.messageKeyWords = Arrays.asList(messageKeyWords); + this.value = value; + } + + public List getKeyWords() { + return messageKeyWords; + } + + public String getValue() { + return value; + } +} diff --git a/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/io/IOUtilTest.java b/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/io/IOUtilTest.java new file mode 100644 index 0000000000..2021bf722c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/test/java/org/opendaylight/controller/netconf/cli/io/IOUtilTest.java @@ -0,0 +1,27 @@ +/* + * 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.netconf.cli.io; + +import com.google.common.collect.Maps; +import java.util.Map; +import junit.framework.Assert; +import org.junit.Test; +import org.opendaylight.controller.netconf.cli.commands.CommandConstants; +import org.opendaylight.yangtools.yang.common.QName; + +public class IOUtilTest { + + @Test + public void testQNameFromKeyStringNew() throws Exception { + final String s = IOUtil.qNameToKeyString(CommandConstants.HELP_QNAME, "module"); + final Map modulesMap = Maps.newHashMap(); + modulesMap.put("module", new QName(CommandConstants.HELP_QNAME, "module")); + final QName qName = IOUtil.qNameFromKeyString(s, modulesMap); + Assert.assertEquals(CommandConstants.HELP_QNAME, qName); + } +} diff --git a/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/ietf-inet-types.yang b/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/ietf-inet-types.yang new file mode 100644 index 0000000000..de20febbb7 --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/ietf-inet-types.yang @@ -0,0 +1,418 @@ + module ietf-inet-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-inet-types"; + prefix "inet"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Partain + + + WG Chair: David Kessens + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types for Internet addresses and related things. + + Copyright (c) 2010 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, is permitted pursuant to, and subject to the license + terms contained in, the Simplified BSD License set forth in Section + 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6021; see + the RFC itself for full legal notices."; + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of protocol field related types ***/ + + typedef ip-version { + type enumeration { + enum unknown { + value "0"; + description + "An unknown or unspecified version of the Internet protocol."; + } + enum ipv4 { + value "1"; + description + "The IPv4 protocol as defined in RFC 791."; + } + enum ipv6 { + value "2"; + description + "The IPv6 protocol as defined in RFC 2460."; + } + } + description + "This value represents the version of the IP protocol. + + In the value set and its semantics, this type is equivalent + to the InetVersion textual convention of the SMIv2."; + reference + "RFC 791: Internet Protocol + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + typedef dscp { + type uint8 { + range "0..63"; + } + description + "The dscp type represents a Differentiated Services Code-Point + that may be used for marking packets in a traffic stream. + + In the value set and its semantics, this type is equivalent + to the Dscp textual convention of the SMIv2."; + reference + "RFC 3289: Management Information Base for the Differentiated + Services Architecture + RFC 2474: Definition of the Differentiated Services Field + (DS Field) in the IPv4 and IPv6 Headers + RFC 2780: IANA Allocation Guidelines For Values In + the Internet Protocol and Related Headers"; + } + + typedef ipv6-flow-label { + type uint32 { + range "0..1048575"; + } + description + "The flow-label type represents flow identifier or Flow Label + in an IPv6 packet header that may be used to discriminate + traffic flows. + + In the value set and its semantics, this type is equivalent + to the IPv6FlowLabel textual convention of the SMIv2."; + reference + "RFC 3595: Textual Conventions for IPv6 Flow Label + RFC 2460: Internet Protocol, Version 6 (IPv6) Specification"; + } + + typedef port-number { + type uint16 { + range "0..65535"; + } + description + "The port-number type represents a 16-bit port number of an + Internet transport layer protocol such as UDP, TCP, DCCP, or + SCTP. Port numbers are assigned by IANA. A current list of + all assignments is available from . + + Note that the port number value zero is reserved by IANA. In + situations where the value zero does not make sense, it can + be excluded by subtyping the port-number type. + + In the value set and its semantics, this type is equivalent + to the InetPortNumber textual convention of the SMIv2."; + reference + "RFC 768: User Datagram Protocol + RFC 793: Transmission Control Protocol + RFC 4960: Stream Control Transmission Protocol + RFC 4340: Datagram Congestion Control Protocol (DCCP) + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + /*** collection of autonomous system related types ***/ + + typedef as-number { + type uint32; + description + "The as-number type represents autonomous system numbers + which identify an Autonomous System (AS). An AS is a set + of routers under a single technical administration, using + an interior gateway protocol and common metrics to route + packets within the AS, and using an exterior gateway + protocol to route packets to other ASs'. IANA maintains + the AS number space and has delegated large parts to the + regional registries. + + Autonomous system numbers were originally limited to 16 + bits. BGP extensions have enlarged the autonomous system + number space to 32 bits. This type therefore uses an uint32 + base type without a range restriction in order to support + a larger autonomous system number space. + + In the value set and its semantics, this type is equivalent + to the InetAutonomousSystemNumber textual convention of + the SMIv2."; + reference + "RFC 1930: Guidelines for creation, selection, and registration + of an Autonomous System (AS) + RFC 4271: A Border Gateway Protocol 4 (BGP-4) + RFC 4893: BGP Support for Four-octet AS Number Space + RFC 4001: Textual Conventions for Internet Network Addresses"; + } + + /*** collection of IP address and hostname related types ***/ + + typedef ip-address { + type union { + type inet:ipv4-address; + type inet:ipv6-address; + } + description + "The ip-address type represents an IP address and is IP + version neutral. The format of the textual representations + implies the IP version."; + } + + typedef ipv4-address { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '(%[\p{N}\p{L}]+)?'; + } + description + "The ipv4-address type represents an IPv4 address in + dotted-quad notation. The IPv4 address may include a zone + index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format for the zone index is the numerical + format"; + } + + typedef ipv6-address { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(%[\p{N}\p{L}]+)?'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(%.+)?'; + } + description + "The ipv6-address type represents an IPv6 address in full, + mixed, shortened, and shortened-mixed notation. The IPv6 + address may include a zone index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format of IPv6 addresses uses the compressed + format described in RFC 4291, Section 2.2, item 2 with the + following additional rules: the :: substitution must be + applied to the longest sequence of all-zero 16-bit chunks + in an IPv6 address. If there is a tie, the first sequence + of all-zero 16-bit chunks is replaced by ::. Single + all-zero 16-bit chunks are not compressed. The canonical + format uses lowercase characters and leading zeros are + not allowed. The canonical format for the zone index is + the numerical format as described in RFC 4007, Section + 11.2."; + reference + "RFC 4291: IP Version 6 Addressing Architecture + RFC 4007: IPv6 Scoped Address Architecture + RFC 5952: A Recommendation for IPv6 Address Text Representation"; + } + + typedef ip-prefix { + type union { + type inet:ipv4-prefix; + type inet:ipv6-prefix; + } + description + "The ip-prefix type represents an IP prefix and is IP + version neutral. The format of the textual representations + implies the IP version."; + } + + typedef ipv4-prefix { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '/(([0-9])|([1-2][0-9])|(3[0-2]))'; + } + description + "The ipv4-prefix type represents an IPv4 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal to 32. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The canonical format of an IPv4 prefix has all bits of + the IPv4 address set to zero that are not part of the + IPv4 prefix."; + } + + typedef ipv6-prefix { + type string { + pattern '((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}' + + '((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|' + + '(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\.){3}' + + '(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))' + + '(/(([0-9])|([0-9]{2})|(1[0-1][0-9])|(12[0-8])))'; + pattern '(([^:]+:){6}(([^:]+:[^:]+)|(.*\..*)))|' + + '((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)' + + '(/.+)'; + } + description + "The ipv6-prefix type represents an IPv6 address prefix. + The prefix length is given by the number following the + slash character and must be less than or equal 128. + + A prefix length value of n corresponds to an IP address + mask that has n contiguous 1-bits from the most + significant bit (MSB) and all other bits set to 0. + + The IPv6 address should have all bits that do not belong + to the prefix set to zero. + + The canonical format of an IPv6 prefix has all bits of + the IPv6 address set to zero that are not part of the + IPv6 prefix. Furthermore, IPv6 address is represented + in the compressed format described in RFC 4291, Section + 2.2, item 2 with the following additional rules: the :: + substitution must be applied to the longest sequence of + all-zero 16-bit chunks in an IPv6 address. If there is + a tie, the first sequence of all-zero 16-bit chunks is + replaced by ::. Single all-zero 16-bit chunks are not + compressed. The canonical format uses lowercase + characters and leading zeros are not allowed."; + reference + "RFC 4291: IP Version 6 Addressing Architecture"; + } + + /*** collection of domain name and URI types ***/ + + typedef domain-name { + type string { + pattern '((([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.)*' + + '([a-zA-Z0-9_]([a-zA-Z0-9\-_]){0,61})?[a-zA-Z0-9]\.?)' + + '|\.'; + length "1..253"; + } + description + "The domain-name type represents a DNS domain name. The + name SHOULD be fully qualified whenever possible. + + Internet domain names are only loosely specified. Section + 3.5 of RFC 1034 recommends a syntax (modified in Section + 2.1 of RFC 1123). The pattern above is intended to allow + for current practice in domain name use, and some possible + future expansion. It is designed to hold various types of + domain names, including names used for A or AAAA records + (host names) and other records, such as SRV records. Note + that Internet host names have a stricter syntax (described + in RFC 952) than the DNS recommendations in RFCs 1034 and + 1123, and that systems that want to store host names in + schema nodes using the domain-name type are recommended to + adhere to this stricter standard to ensure interoperability. + + The encoding of DNS names in the DNS protocol is limited + to 255 characters. Since the encoding consists of labels + prefixed by a length bytes and there is a trailing NULL + byte, only 253 characters can appear in the textual dotted + notation. + + The description clause of schema nodes using the domain-name + type MUST describe when and how these names are resolved to + IP addresses. Note that the resolution of a domain-name value + may require to query multiple DNS records (e.g., A for IPv4 + and AAAA for IPv6). The order of the resolution process and + which DNS record takes precedence can either be defined + explicitely or it may depend on the configuration of the + resolver. + + Domain-name values use the US-ASCII encoding. Their canonical + format uses lowercase US-ASCII characters. Internationalized + domain names MUST be encoded in punycode as described in RFC + 3492"; + reference + "RFC 952: DoD Internet Host Table Specification + RFC 1034: Domain Names - Concepts and Facilities + RFC 1123: Requirements for Internet Hosts -- Application + and Support + RFC 2782: A DNS RR for specifying the location of services + (DNS SRV) + RFC 3492: Punycode: A Bootstring encoding of Unicode for + Internationalized Domain Names in Applications + (IDNA) + RFC 5891: Internationalizing Domain Names in Applications + (IDNA): Protocol"; + } + + typedef host { + type union { + type inet:ip-address; + type inet:domain-name; + } + description + "The host type represents either an IP address or a DNS + domain name."; + } + + typedef uri { + type string; + description + "The uri type represents a Uniform Resource Identifier + (URI) as defined by STD 66. + + Objects using the uri type MUST be in US-ASCII encoding, + and MUST be normalized as described by RFC 3986 Sections + 6.2.1, 6.2.2.1, and 6.2.2.2. All unnecessary + percent-encoding is removed, and all case-insensitive + characters are set to lowercase except for hexadecimal + digits, which are normalized to uppercase as described in + Section 6.2.2.1. + + The purpose of this normalization is to help provide + unique URIs. Note that this normalization is not + sufficient to provide uniqueness. Two URIs that are + textually distinct after this normalization may still be + equivalent. + + Objects using the uri type may restrict the schemes that + they permit. For example, 'data:' and 'urn:' schemes + might not be appropriate. + + A zero-length URI is not a valid URI. This can be used to + express 'URI absent' where required. + + In the value set and its semantics, this type is equivalent + to the Uri SMIv2 textual convention defined in RFC 5017."; + reference + "RFC 3986: Uniform Resource Identifier (URI): Generic Syntax + RFC 3305: Report from the Joint W3C/IETF URI Planning Interest + Group: Uniform Resource Identifiers (URIs), URLs, + and Uniform Resource Names (URNs): Clarifications + and Recommendations + RFC 5017: MIB Textual Conventions for Uniform Resource + Identifiers (URIs)"; + } + + } diff --git a/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/ietf-netconf.yang b/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/ietf-netconf.yang new file mode 100644 index 0000000000..44c19c329a --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/ietf-netconf.yang @@ -0,0 +1,927 @@ +module ietf-netconf { + + // the namespace for NETCONF XML definitions is unchanged + // from RFC 4741, which this document replaces + namespace "urn:ietf:params:xml:ns:netconf:base:1.0"; + + prefix nc; + + import ietf-inet-types { + prefix inet; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: Bert Wijnen + + + WG Chair: Mehmet Ersue + + + Editor: Martin Bjorklund + + + Editor: Juergen Schoenwaelder + + + Editor: Andy Bierman + "; + description + "NETCONF Protocol Data Types and Protocol Operations. + + Copyright (c) 2011 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6241; see + the RFC itself for full legal notices."; + revision 2011-06-01 { + description + "Initial revision"; + reference + "RFC 6241: Network Configuration Protocol"; + } + + extension get-filter-element-attributes { + description + "If this extension is present within an 'anyxml' + statement named 'filter', which must be conceptually + defined within the RPC input section for the + and protocol operations, then the + following unqualified XML attribute is supported + within the element, within a or + protocol operation: + + type : optional attribute with allowed + value strings 'subtree' and 'xpath'. + If missing, the default value is 'subtree'. + + If the 'xpath' feature is supported, then the + following unqualified XML attribute is + also supported: + + select: optional attribute containing a + string representing an XPath expression. + The 'type' attribute must be equal to 'xpath' + if this attribute is present."; + } + + // NETCONF capabilities defined as features + feature writable-running { + description + "NETCONF :writable-running capability; + If the server advertises the :writable-running + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.2"; + } + + feature candidate { + description + "NETCONF :candidate capability; + If the server advertises the :candidate + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.3"; + } + + feature confirmed-commit { + if-feature candidate; + description + "NETCONF :confirmed-commit:1.1 capability; + If the server advertises the :confirmed-commit:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + + reference "RFC 6241, Section 8.4"; + } + + feature rollback-on-error { + description + "NETCONF :rollback-on-error capability; + If the server advertises the :rollback-on-error + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.5"; + } + + feature validate { + description + "NETCONF :validate:1.1 capability; + If the server advertises the :validate:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.6"; + } + + feature startup { + description + "NETCONF :startup capability; + If the server advertises the :startup + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.7"; + } + + feature url { + description + "NETCONF :url capability; + If the server advertises the :url + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.8"; + } + + feature xpath { + description + "NETCONF :xpath capability; + If the server advertises the :xpath + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.9"; + } + + // NETCONF Simple Types + + typedef session-id-type { + type uint32 { + range "1..max"; + } + description + "NETCONF Session Id"; + } + + typedef session-id-or-zero-type { + type uint32; + description + "NETCONF Session Id or Zero to indicate none"; + } + typedef error-tag-type { + type enumeration { + enum in-use { + description + "The request requires a resource that + already is in use."; + } + enum invalid-value { + description + "The request specifies an unacceptable value for one + or more parameters."; + } + enum too-big { + description + "The request or response (that would be generated) is + too large for the implementation to handle."; + } + enum missing-attribute { + description + "An expected attribute is missing."; + } + enum bad-attribute { + description + "An attribute value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-attribute { + description + "An unexpected attribute is present."; + } + enum missing-element { + description + "An expected element is missing."; + } + enum bad-element { + description + "An element value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-element { + description + "An unexpected element is present."; + } + enum unknown-namespace { + description + "An unexpected namespace is present."; + } + enum access-denied { + description + "Access to the requested protocol operation or + data model is denied because authorization failed."; + } + enum lock-denied { + description + "Access to the requested lock is denied because the + lock is currently held by another entity."; + } + enum resource-denied { + description + "Request could not be completed because of + insufficient resources."; + } + enum rollback-failed { + description + "Request to roll back some configuration change (via + rollback-on-error or operations) + was not completed for some reason."; + + } + enum data-exists { + description + "Request could not be completed because the relevant + data model content already exists. For example, + a 'create' operation was attempted on data that + already exists."; + } + enum data-missing { + description + "Request could not be completed because the relevant + data model content does not exist. For example, + a 'delete' operation was attempted on + data that does not exist."; + } + enum operation-not-supported { + description + "Request could not be completed because the requested + operation is not supported by this implementation."; + } + enum operation-failed { + description + "Request could not be completed because the requested + operation failed for some reason not covered by + any other error condition."; + } + enum partial-operation { + description + "This error-tag is obsolete, and SHOULD NOT be sent + by servers conforming to this document."; + } + enum malformed-message { + description + "A message could not be handled because it failed to + be parsed correctly. For example, the message is not + well-formed XML or it uses an invalid character set."; + } + } + description "NETCONF Error Tag"; + reference "RFC 6241, Appendix A"; + } + + typedef error-severity-type { + type enumeration { + enum error { + description "Error severity"; + } + enum warning { + description "Warning severity"; + } + } + description "NETCONF Error Severity"; + reference "RFC 6241, Section 4.3"; + } + + typedef edit-operation-type { + type enumeration { + enum merge { + description + "The configuration data identified by the + element containing this attribute is merged + with the configuration at the corresponding + level in the configuration datastore identified + by the target parameter."; + } + enum replace { + description + "The configuration data identified by the element + containing this attribute replaces any related + configuration in the configuration datastore + identified by the target parameter. If no such + configuration data exists in the configuration + datastore, it is created. Unlike a + operation, which replaces the + entire target configuration, only the configuration + actually present in the config parameter is affected."; + } + enum create { + description + "The configuration data identified by the element + containing this attribute is added to the + configuration if and only if the configuration + data does not already exist in the configuration + datastore. If the configuration data exists, an + element is returned with an + value of 'data-exists'."; + } + enum delete { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if and only if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, an element is returned with + an value of 'data-missing'."; + } + enum remove { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, the 'remove' operation is silently ignored + by the server."; + } + } + default "merge"; + description "NETCONF 'operation' attribute values"; + reference "RFC 6241, Section 7.2"; + } + + // NETCONF Standard Protocol Operations + + rpc get-config { + description + "Retrieve all or part of a specified configuration."; + + reference "RFC 6241, Section 7.1"; + + input { + container source { + description + "Particular configuration to retrieve."; + + choice config-source { + mandatory true; + description + "The configuration to retrieve."; + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source. + This is optional-to-implement on the server because + not all servers will support filtering for this + datastore."; + } + } + } + + anyxml filter { + description + "Subtree or XPath filter to use."; + nc:get-filter-element-attributes; + } + } + + output { + anyxml data { + description + "Copy of the source datastore subset that matched + the filter criteria (if any). An empty data container + indicates that the request did not produce any results."; + } + } + } + + rpc edit-config { + description + "The operation loads all or part of a specified + configuration to the specified target configuration."; + + reference "RFC 6241, Section 7.2"; + + input { + container target { + description + "Particular configuration to edit."; + + choice config-target { + mandatory true; + description + "The configuration target."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config source."; + } + } + } + + leaf default-operation { + type enumeration { + enum merge { + description + "The default operation is merge."; + } + enum replace { + description + "The default operation is replace."; + } + enum none { + description + "There is no default operation."; + } + } + default "merge"; + description + "The default operation to use."; + } + + leaf test-option { + if-feature validate; + type enumeration { + enum test-then-set { + description + "The server will test and then set if no errors."; + } + enum set { + description + "The server will set without a test first."; + } + + enum test-only { + description + "The server will only test and not set, even + if there are no errors."; + } + } + default "test-then-set"; + description + "The test option to use."; + } + + leaf error-option { + type enumeration { + enum stop-on-error { + description + "The server will stop on errors."; + } + enum continue-on-error { + description + "The server may continue on errors."; + } + enum rollback-on-error { + description + "The server will roll back on errors. + This value can only be used if the 'rollback-on-error' + feature is supported."; + } + } + default "stop-on-error"; + description + "The error option to use."; + } + + choice edit-content { + mandatory true; + description + "The content for the edit operation."; + + anyxml config { + description + "Inline Config content."; + } + leaf url { + if-feature url; + type inet:uri; + description + "URL-based config content."; + } + } + } + } + + rpc copy-config { + description + "Create or replace an entire configuration datastore with the + contents of another complete configuration datastore."; + + reference "RFC 6241, Section 7.3"; + + input { + container target { + description + "Particular configuration to copy to."; + + choice config-target { + mandatory true; + description + "The configuration target of the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config target. + This is optional-to-implement on the server."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + + container source { + description + "Particular configuration to copy from."; + + choice config-source { + mandatory true; + description + "The configuration source for the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } + + rpc delete-config { + description + "Delete a configuration datastore."; + + reference "RFC 6241, Section 7.4"; + + input { + container target { + description + "Particular configuration to delete."; + + choice config-target { + mandatory true; + description + "The configuration target to delete."; + + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + } + } + + rpc lock { + description + "The lock operation allows the client to lock the configuration + system of a device."; + + reference "RFC 6241, Section 7.5"; + + input { + container target { + description + "Particular configuration to lock."; + + choice config-target { + mandatory true; + description + "The configuration target to lock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc unlock { + description + "The unlock operation is used to release a configuration lock, + previously obtained with the 'lock' operation."; + + reference "RFC 6241, Section 7.6"; + + input { + container target { + description + "Particular configuration to unlock."; + + choice config-target { + mandatory true; + description + "The configuration target to unlock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc get { + description + "Retrieve running configuration and device state information."; + + reference "RFC 6241, Section 7.7"; + + input { + anyxml filter { + description + "This parameter specifies the portion of the system + configuration and state data to retrieve."; + nc:get-filter-element-attributes; + } + } + + output { + anyxml data { + description + "Copy of the running datastore subset and/or state + data that matched the filter criteria (if any). + An empty data container indicates that the request did not + produce any results."; + } + } + } + + rpc close-session { + description + "Request graceful termination of a NETCONF session."; + + reference "RFC 6241, Section 7.8"; + } + + rpc kill-session { + description + "Force the termination of a NETCONF session."; + + reference "RFC 6241, Section 7.9"; + + input { + leaf session-id { + type session-id-type; + mandatory true; + description + "Particular session to kill."; + } + } + } + + rpc commit { + if-feature candidate; + + description + "Commit the candidate configuration as the device's new + current configuration."; + + reference "RFC 6241, Section 8.3.4.1"; + + input { + leaf confirmed { + if-feature confirmed-commit; + type empty; + description + "Requests a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf confirm-timeout { + if-feature confirmed-commit; + type uint32 { + range "1..max"; + } + units "seconds"; + default "600"; // 10 minutes + description + "The timeout interval for a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist { + if-feature confirmed-commit; + type string; + description + "This parameter is used to make a confirmed commit + persistent. A persistent confirmed commit is not aborted + if the NETCONF session terminates. The only way to abort + a persistent confirmed commit is to let the timer expire, + or to use the operation. + + The value of this parameter is a token that must be given + in the 'persist-id' parameter of or + operations in order to confirm or cancel + the persistent confirmed commit. + + The token should be a random string."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist-id { + if-feature confirmed-commit; + type string; + description + "This parameter is given in order to commit a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + reference "RFC 6241, Section 8.3.4.1"; + } + + } + } + + rpc discard-changes { + if-feature candidate; + + description + "Revert the candidate configuration to the current + running configuration."; + reference "RFC 6241, Section 8.3.4.2"; + } + + rpc cancel-commit { + if-feature confirmed-commit; + description + "This operation is used to cancel an ongoing confirmed commit. + If the confirmed commit is persistent, the parameter + 'persist-id' must be given, and it must match the value of the + 'persist' parameter."; + reference "RFC 6241, Section 8.4.4.1"; + + input { + leaf persist-id { + type string; + description + "This parameter is given in order to cancel a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + } + } + } + + rpc validate { + if-feature validate; + + description + "Validates the contents of the specified configuration."; + + reference "RFC 6241, Section 8.6.4.1"; + + input { + container source { + description + "Particular configuration to validate."; + + choice config-source { + mandatory true; + description + "The configuration source to validate."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } + +} diff --git a/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/model1.yang b/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/model1.yang new file mode 100644 index 0000000000..c84d55fe9c --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/model1.yang @@ -0,0 +1,74 @@ +module model1 { + namespace "ns:model1"; + prefix "mod1"; + + revision "2014-05-14" { + } + + container cont1 { + container cont11 { + list lst111 { + key lf1111; + leaf lf1111 { + type int32; + } + container cont111 { + leaf lf1112 { + type string; + } + leaf-list lflst1111 { + type int8; + } + } + } + } + + container cont12 { + list lst121 { + key lf1211; + leaf lf1211 { + type string; + } + list lst1211 { + leaf lf12111 { + type uint8; + } + leaf lf12112 { + type string; + } + } + } + choice chcA { + case AA { + leaf lf12AA1 { + type string; + } + } + case AB { + container cont12AB1 { + leaf lf12AB1 { + type string; + } + } + } + leaf lf121 { //should be standalone case + type string; + } + } + list lst122 { + } + } + } + + container cont2 { + container cont23 { + } + } + + container contA { + } + + container contB { + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/model2.yang b/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/model2.yang new file mode 100644 index 0000000000..e92cbd73da --- /dev/null +++ b/opendaylight/netconf/netconf-cli/src/test/resources/schema-context/model2.yang @@ -0,0 +1,32 @@ +module model2 { + namespace "ns:model2"; + prefix "mod2"; + + import model1 {prefix model1; revision-date 2014-05-14;} + + revision "2014-05-14" { + } + + container contA { + } + + container contB { + } + + augment "/model1:cont2" { + container cont21 { + } + + container cont22 { + } + } + + augment "/model1:cont1" { + list lst11 { + leaf lf111 { + type string; + } + } + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index d26fcf987e..937949a17e 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -19,6 +19,7 @@ netconf-api + netconf-cli netconf-impl config-netconf-connector netconf-util